diff --git a/Swashbuckle.Core/Swagger/Annotations/SwaggerDescriptionAttribute.cs b/Swashbuckle.Core/Swagger/Annotations/SwaggerDescriptionAttribute.cs new file mode 100644 index 000000000..9ad83379d --- /dev/null +++ b/Swashbuckle.Core/Swagger/Annotations/SwaggerDescriptionAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace Swashbuckle.Swagger.Annotations +{ + public class SwaggerDescriptionAttribute : Attribute + { + public SwaggerDescriptionAttribute(string description = null, string summary = null) + { + Description = description; + Summary = summary; + } + + public string Description { get; set; } + public string Summary { get; set; } + } +} diff --git a/Swashbuckle.Core/Swagger/SchemaExtensions.cs b/Swashbuckle.Core/Swagger/SchemaExtensions.cs index a1c5c63bd..41d050e98 100644 --- a/Swashbuckle.Core/Swagger/SchemaExtensions.cs +++ b/Swashbuckle.Core/Swagger/SchemaExtensions.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.ComponentModel.DataAnnotations; using Newtonsoft.Json.Serialization; +using Swashbuckle.Swagger.Annotations; namespace Swashbuckle.Swagger { @@ -46,6 +47,17 @@ public static Schema WithValidationProperties(this Schema schema, JsonProperty j return schema; } + public static Schema WithDescriptionProperty(this Schema schema, JsonProperty jsonProperty) + { + var propInfo = jsonProperty.PropertyInfo(); + if (propInfo == null) + return schema; + + var attrib = propInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + schema.description = attrib != null ? attrib.Description : null; + return schema; + } + public static void PopulateFrom(this PartialSchema partialSchema, Schema schema) { if (schema == null) return; diff --git a/Swashbuckle.Core/Swagger/SchemaRegistry.cs b/Swashbuckle.Core/Swagger/SchemaRegistry.cs index 4a37f92e6..083e02787 100644 --- a/Swashbuckle.Core/Swagger/SchemaRegistry.cs +++ b/Swashbuckle.Core/Swagger/SchemaRegistry.cs @@ -240,7 +240,7 @@ private Schema CreateObjectSchema(JsonObjectContract jsonContract) .Where(p => !(_ignoreObsoleteProperties && p.IsObsolete())) .ToDictionary( prop => prop.PropertyName, - prop => CreateInlineSchema(prop.PropertyType).WithValidationProperties(prop) + prop => CreateInlineSchema(prop.PropertyType).WithValidationProperties(prop).WithDescriptionProperty(prop) ); var required = jsonContract.Properties.Where(prop => prop.IsRequired()) diff --git a/Swashbuckle.Core/Swagger/SwaggerGenerator.cs b/Swashbuckle.Core/Swagger/SwaggerGenerator.cs index 851adfc5d..96b1f6129 100644 --- a/Swashbuckle.Core/Swagger/SwaggerGenerator.cs +++ b/Swashbuckle.Core/Swagger/SwaggerGenerator.cs @@ -3,10 +3,8 @@ using System.Web.Http.Description; using System; using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using System.Net.Http.Formatting; -using System.Net.Http; using System.Threading; +using Swashbuckle.Swagger.Annotations; namespace Swashbuckle.Swagger { @@ -67,7 +65,7 @@ public SwaggerDocument GetSwagger(string rootUrl, string apiVersion) securityDefinitions = _options.SecurityDefinitions }; - foreach(var filter in _options.DocumentFilters) + foreach (var filter in _options.DocumentFilters) { filter.Apply(swaggerDoc, schemaRegistry, _apiExplorer); } @@ -142,6 +140,7 @@ private Operation CreateOperation(ApiDescription apiDesc, SchemaRegistry schemaR }) .ToList(); + var description = apiDesc.ActionDescriptor.GetCustomAttributes().FirstOrDefault(); var responses = new Dictionary(); var responseType = apiDesc.ResponseType(); if (responseType == null || responseType == typeof(void)) @@ -152,12 +151,14 @@ private Operation CreateOperation(ApiDescription apiDesc, SchemaRegistry schemaR var operation = new Operation { tags = new[] { _options.GroupingKeySelector(apiDesc) }, + description = description != null ? description.Description : null, + summary = description != null ? description.Summary : null, operationId = apiDesc.FriendlyId(), produces = apiDesc.Produces().ToList(), consumes = apiDesc.Consumes().ToList(), parameters = parameters.Any() ? parameters : null, // parameters can be null but not empty responses = responses, - deprecated = apiDesc.IsObsolete() ? true : (bool?) null + deprecated = apiDesc.IsObsolete() ? true : (bool?)null }; foreach (var filter in _options.OperationFilters) @@ -191,12 +192,17 @@ private Parameter CreateParameter(string location, ApiParameterDescription param { parameter.type = "string"; parameter.required = true; - return parameter; + return parameter; } parameter.required = location == "path" || !paramDesc.ParameterDescriptor.IsOptional; parameter.@default = paramDesc.ParameterDescriptor.DefaultValue; + var description = paramDesc.ParameterDescriptor.GetCustomAttributes().FirstOrDefault(); + if (description != null) + { + parameter.description = description.Description; + } var schema = schemaRegistry.GetOrRegister(paramDesc.ParameterDescriptor.ParameterType); if (parameter.@in == "body") parameter.schema = schema; diff --git a/Swashbuckle.Core/Swashbuckle.Core.csproj b/Swashbuckle.Core/Swashbuckle.Core.csproj index d830ccbba..1b16af32a 100644 --- a/Swashbuckle.Core/Swashbuckle.Core.csproj +++ b/Swashbuckle.Core/Swashbuckle.Core.csproj @@ -70,6 +70,7 @@ + diff --git a/Swashbuckle.Dummy.Core/Controllers/SwaggerAnnotatedController.cs b/Swashbuckle.Dummy.Core/Controllers/SwaggerAnnotatedController.cs index c091fa55e..97acdc3d8 100644 --- a/Swashbuckle.Dummy.Core/Controllers/SwaggerAnnotatedController.cs +++ b/Swashbuckle.Dummy.Core/Controllers/SwaggerAnnotatedController.cs @@ -32,7 +32,8 @@ public Message GetById(int id) [HttpPut] [SwaggerOperation("UpdateMessage", Tags = new[] { "messages" }, Schemes = new[] { "ws" })] - public void Put(int id, Message message) + + public void Put([SwaggerDescription("param description")]int id, Message message) { throw new NotImplementedException(); } @@ -41,6 +42,7 @@ public void Put(int id, Message message) [SwaggerSchemaFilter(typeof(AddMessageDefault))] public class Message { + [SwaggerDescription("param model description")] public string Title { get; set; } public string Content { get; set; } } diff --git a/Swashbuckle.Dummy.SelfHost/Swashbuckle.Dummy.SelfHost.csproj b/Swashbuckle.Dummy.SelfHost/Swashbuckle.Dummy.SelfHost.csproj index 47a767487..93eba0afd 100644 --- a/Swashbuckle.Dummy.SelfHost/Swashbuckle.Dummy.SelfHost.csproj +++ b/Swashbuckle.Dummy.SelfHost/Swashbuckle.Dummy.SelfHost.csproj @@ -1,5 +1,5 @@  - + Debug @@ -9,10 +9,11 @@ Properties Swashbuckle.Dummy.SelfHost Swashbuckle.Dummy.SelfHost - v4.5 + v4.6 512 ..\ true + AnyCPU diff --git a/Swashbuckle.Dummy.SelfHost/app.config b/Swashbuckle.Dummy.SelfHost/app.config index 4f888434c..d1858bfd1 100644 --- a/Swashbuckle.Dummy.SelfHost/app.config +++ b/Swashbuckle.Dummy.SelfHost/app.config @@ -1,19 +1,19 @@ - + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/Swashbuckle.Tests/Swagger/AnnotationsTests.cs b/Swashbuckle.Tests/Swagger/AnnotationsTests.cs index 45ca5ebbe..e7c145016 100644 --- a/Swashbuckle.Tests/Swagger/AnnotationsTests.cs +++ b/Swashbuckle.Tests/Swagger/AnnotationsTests.cs @@ -138,5 +138,14 @@ public void It_supports_per_action_filters_via_swagger_operation_filter_attribut Assert.AreEqual(expected.ToString(), responseExamples.ToString()); } + + [Test] + public void It_has_parameter_descriptions() + { + var swagger = GetContent("http://tempuri.org/swagger/docs/v1"); + var description = (string)swagger["paths"]["/swaggerannotated/{id}"]["put"]["parameters"][0]["description"]; + + Assert.AreEqual("param description", description); + } } }