From 79d3543c778883dbe09169c97f22814907987a66 Mon Sep 17 00:00:00 2001 From: RFehlow Date: Tue, 20 Dec 2016 14:32:44 +0100 Subject: [PATCH 1/3] Fixed issue #56 (multiple header attributes), Added full support for the security definition (example in readme) --- README.md | 171 ++++++++++-------- src/SwaggerWcf.Test.Service/Global.asax.cs | 30 +++ src/SwaggerWcf.Test.Service/IStore.cs | 1 + .../Attributes/SwaggerWcfHeaderAttribute.cs | 2 +- .../Attributes/SwaggerWcfSecurityAttribute.cs | 33 ++++ src/SwaggerWcf/Models/PathAction.cs | 28 ++- .../Models/SecurityAuthorization.cs | 118 ++++++++++++ src/SwaggerWcf/Models/SecurityDefinitions.cs | 9 + src/SwaggerWcf/Models/Service.cs | 20 ++ src/SwaggerWcf/Support/Mapper.cs | 24 ++- src/SwaggerWcf/SwaggerWcf.csproj | 3 + src/SwaggerWcf/SwaggerWcfEndpoint.cs | 4 +- 12 files changed, 366 insertions(+), 77 deletions(-) create mode 100644 src/SwaggerWcf/Attributes/SwaggerWcfSecurityAttribute.cs create mode 100644 src/SwaggerWcf/Models/SecurityAuthorization.cs create mode 100644 src/SwaggerWcf/Models/SecurityDefinitions.cs diff --git a/README.md b/README.md index c9411e0..a6b169b 100644 --- a/README.md +++ b/README.md @@ -111,100 +111,125 @@ Notes: * `tags` will be described further down #### Configure via code +Configure the base properties via code. New: You can add security settings to your api (see also the new Security-Methodattribute) + ```csharp -SwaggerWcfEndpoint.Configure(new SwaggerWcf.Models.Info +var info = new Info { - Description = "Sample Service to test SwaggerWCF", - Version = "0.0.1" - // etc -}); -``` +Description = "Sample Service to test SwaggerWCF", +Version = "0.0.1" +// etc +}; -### Step 5: Decorate WCF services interfaces +var security = new SecurityDefinitions +{ + { + "api-gateway", new SecurityAuthorization + { + Type = "oauth2", + Name = "api-gateway", + Description = "Forces authentication with credentials via an api gateway", + Flow = "password", + Scopes = new Dictionary + { + { "author", "use author scope"}, + { "admin", "use admin scope"}, + }, + AuthorizationUrl = "http://yourapi.net/oauth/token" + } + } +}; -For each method, configure the `WebInvoke` or `WebGet` attribute, and add a `SwaggerWcfPath` attribute. +SwaggerWcfEndpoint.Configure(info, security); + ``` -```csharp -[ServiceContract] -public interface IStore -{ - [SwaggerWcfPath("Create book", "Create a book on the store")] - [WebInvoke(UriTemplate = "/books", - BodyStyle = WebMessageBodyStyle.Bare, - Method = "POST", - RequestFormat = WebMessageFormat.Json, - ResponseFormat = WebMessageFormat.Json)] - [OperationContract] - Book CreateBook(Book value); - - // [.......] -} -``` + ### Step 5: Decorate WCF services interfaces -### Step 6: Decorate WCF services class + For each method, configure the `WebInvoke` or `WebGet` attribute, and add a `SwaggerWcfPath` attribute. -Add the `SwaggerWcf` and `AspNetCompatibilityRequirements` attributes to the class providing the base path for the service (the same as used in step 2). -Optinally, for each method, add the `SwaggerWcfTag` to categorize the method and the `SwaggerWcfResponse` for each possible response from the service. + ```csharp -```csharp + [ServiceContract] + public interface IStore + { + [SwaggerWcfPath("Create book", "Create a book on the store")] + [WebInvoke(UriTemplate = "/books", + BodyStyle = WebMessageBodyStyle.Bare, + Method = "POST", + RequestFormat = WebMessageFormat.Json, + ResponseFormat = WebMessageFormat.Json)] + [OperationContract] + Book CreateBook(Book value); -[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] -[SwaggerWcf("/v1/rest")] -public class BookStore : IStore -{ - [SwaggerWcfTag("Books")] - [SwaggerWcfResponse(HttpStatusCode.Created, "Book created, value in the response body with id updated")] - [SwaggerWcfResponse(HttpStatusCode.BadRequest, "Bad request", true)] - [SwaggerWcfResponse(HttpStatusCode.InternalServerError, - "Internal error (can be forced using ERROR_500 as book title)", true)] - public Book CreateBook(Book value) - { - // [.......] - } - - // [.......] -} + // [.......] + } -``` + ``` -### Step 7: Decorate data types used in WCF services + ### Step 6: Decorate WCF services class -```csharp + Add the `SwaggerWcf` and `AspNetCompatibilityRequirements` attributes to the class providing the base path for the service (the same as used in step 2). + Optinally, for each method, add the `SwaggerWcfTag` to categorize the method and the `SwaggerWcfResponse` for each possible response from the service. -[DataContract(Name = "book")] -[Description("Book with title, first publish date, author and language")] -[SwaggerWcfDefinition(ExternalDocsUrl = "http://en.wikipedia.org/wiki/Book", ExternalDocsDescription = "Description of a book")] -public class Book -{ - [DataMember(Name = "id")] - [Description("Book ID")] - public string Id { get; set; } + ```csharp - // [.......] -} + [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] + [SwaggerWcf("/v1/rest")] + public class BookStore : IStore + { + [SwaggerWcfTag("Books")] + [SwaggerWcfResponse(HttpStatusCode.Created, "Book created, value in the response body with id updated")] + [SwaggerWcfResponse(HttpStatusCode.BadRequest, "Bad request", true)] + [SwaggerWcfResponse(HttpStatusCode.InternalServerError, + "Internal error (can be forced using ERROR_500 as book title)", true)] + public Book CreateBook(Book value) + { + // [.......] + } -``` + // [.......] + } + + ``` + + ### Step 7: Decorate data types used in WCF services + + ```csharp + + [DataContract(Name = "book")] + [Description("Book with title, first publish date, author and language")] + [SwaggerWcfDefinition(ExternalDocsUrl = "http://en.wikipedia.org/wiki/Book", ExternalDocsDescription = "Description of a book")] + public class Book + { + [DataMember(Name = "id")] + [Description("Book ID")] + public string Id { get; set; } + + // [.......] + } -Note: make sure you add at least the `DataContract` and `DataMember` attributes in classes and properties + ``` -## Attributes + Note: make sure you add at least the `DataContract` and `DataMember` attributes in classes and properties -| Attribute | Used in | Description | Options | -| ---------------------- |------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------- | -| `SwaggerWcf` | `Class`, `Interface` | Enable parsing WCF service | `ServicePath` | -| `SwaggerWcfHidden` | `Class`, `Method`, `Property`, `Parameter` | Hide element from Swagger | | -| `SwaggerWcfTag` | `Class`, `Method`, `Property`, `Parameter` | Add a tag to an element | `TagName`, `HideFromSpec` | -| `SwaggerWcfHeader` | `Method` | Configure method HTTP headers | `Name`, `Required`, `Description`, `DefaultValue` | -| `SwaggerWcfPath` | `Method` | Configure a method in Swagger | `Summary`, `Description`, `OperationId`, `ExternalDocsDescription`, `ExternalDocsUrl`, `Deprecated` | -| `SwaggerWcfParameter` | `Parameter` | Configure method parameters | `Required`, `Description` | -| `SwaggerWcfProperty` | `Property` | Configure property parameters | `Required`, `Description`, `Minimum`, `Maximum`, `Default`, ... | -| `SwaggerWcfResponse` | `Method` | Configure method return value | `Code`, `Description`, `EmptyResponseOverride`, `Headers` | -| `SwaggerWcfDefinition` | `Class` | Configure a data type | `ExternalDocsDescription`, `ExternalDocsUrl` | -| `SwaggerWcfReturnType` | `Method` | Override method return type | `ReturnType` | -| `SwaggerWcfContentTypes` | `Method` | Override consume/produce content-types | `ConsumeTypes`, `ProduceTypes` | + ## Attributes + | Attribute | Used in | Description | Options | + | ---------------------- |------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------- | + | `SwaggerWcf` | `Class`, `Interface` | Enable parsing WCF service | `ServicePath` | + | `SwaggerWcfHidden` | `Class`, `Method`, `Property`, `Parameter` | Hide element from Swagger | | + | `SwaggerWcfTag` | `Class`, `Method`, `Property`, `Parameter` | Add a tag to an element | `TagName`, `HideFromSpec` | + | `SwaggerWcfHeader` | `Method` | Configure method HTTP headers | `Name`, `Required`, `Description`, `DefaultValue` | + | `SwaggerWcfPath` | `Method` | Configure a method in Swagger | `Summary`, `Description`, `OperationId`, `ExternalDocsDescription`, `ExternalDocsUrl`, `Deprecated` | + | `SwaggerWcfParameter` | `Parameter` | Configure method parameters | `Required`, `Description` | + | `SwaggerWcfProperty` | `Property` | Configure property parameters | `Required`, `Description`, `Minimum`, `Maximum`, `Default`, ... | + | `SwaggerWcfResponse` | `Method` | Configure method return value | `Code`, `Description`, `EmptyResponseOverride`, `Headers` | + | `SwaggerWcfDefinition` | `Class` | Configure a data type | `ExternalDocsDescription`, `ExternalDocsUrl` | + | `SwaggerWcfReturnType` | `Method` | Override method return type | `ReturnType` | + | `SwaggerWcfContentTypes` | `Method` | Override consume/produce content-types | `ConsumeTypes`, `ProduceTypes` | + | `SwaggerWcfSecurity` | `Method` | Add security background to this method | `SecurityDefinitionName`, `params Scopes`| ## Tags diff --git a/src/SwaggerWcf.Test.Service/Global.asax.cs b/src/SwaggerWcf.Test.Service/Global.asax.cs index 80f0888..898251a 100644 --- a/src/SwaggerWcf.Test.Service/Global.asax.cs +++ b/src/SwaggerWcf.Test.Service/Global.asax.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.ServiceModel.Activation; using System.Web; using System.Web.Routing; +using SwaggerWcf.Models; namespace SwaggerWcf.Test.Service { @@ -11,6 +13,34 @@ protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.Add(new ServiceRoute("v1/rest", new WebServiceHostFactory(), typeof(BookStore))); RouteTable.Routes.Add(new ServiceRoute("api-docs", new WebServiceHostFactory(), typeof(SwaggerWcfEndpoint))); + + var info = new Info + { + Description = "Sample Service to test SwaggerWCF", + Version = "0.0.1" + // etc + }; + + var security = new SecurityDefinitions + { + { + "api-gateway", new SecurityAuthorization + { + Type = "oauth2", + Name = "api-gateway", + Description = "Forces authentication with credentials via an api gateway", + Flow = "implicit", + Scopes = new Dictionary + { + { "fu", "use fu scope"}, + { "bar", "use bar scope"}, + }, + AuthorizationUrl = "http://yourapi.net/oauth/token" + } + } + }; + + SwaggerWcfEndpoint.Configure(info, security); } protected void Session_Start(object sender, EventArgs e) diff --git a/src/SwaggerWcf.Test.Service/IStore.cs b/src/SwaggerWcf.Test.Service/IStore.cs index c3a460a..8276eb6 100644 --- a/src/SwaggerWcf.Test.Service/IStore.cs +++ b/src/SwaggerWcf.Test.Service/IStore.cs @@ -11,6 +11,7 @@ public interface IStore { #region /books + [SwaggerWcfSecurity("api-gateway", "fu", "bar")] [SwaggerWcfPath("Create book", "Create a book on the store")] // default Method for WebInvoke is POST [WebInvoke(UriTemplate = "/books", BodyStyle = WebMessageBodyStyle.Wrapped, //Method = "POST", diff --git a/src/SwaggerWcf/Attributes/SwaggerWcfHeaderAttribute.cs b/src/SwaggerWcf/Attributes/SwaggerWcfHeaderAttribute.cs index a93eb87..6a17ca6 100644 --- a/src/SwaggerWcf/Attributes/SwaggerWcfHeaderAttribute.cs +++ b/src/SwaggerWcf/Attributes/SwaggerWcfHeaderAttribute.cs @@ -5,7 +5,7 @@ namespace SwaggerWcf.Attributes /// /// Describe a parameter /// - [AttributeUsage(AttributeTargets.Method)] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class SwaggerWcfHeaderAttribute : Attribute { /// diff --git a/src/SwaggerWcf/Attributes/SwaggerWcfSecurityAttribute.cs b/src/SwaggerWcf/Attributes/SwaggerWcfSecurityAttribute.cs new file mode 100644 index 0000000..fb6c8e4 --- /dev/null +++ b/src/SwaggerWcf/Attributes/SwaggerWcfSecurityAttribute.cs @@ -0,0 +1,33 @@ +using System; + +namespace SwaggerWcf.Attributes +{ + /// + /// Attribute to link operation to a security definition + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class SwaggerWcfSecurityAttribute : Attribute + { + /// + /// Specify security definition for this operation + /// + /// Name of the Security Definition + /// Scopes of the security definition + public SwaggerWcfSecurityAttribute(string securityDefinitionName, params string[] scopes) + { + SecurityDefinitionName = securityDefinitionName; + SecurityDefinitionScopes = scopes; + } + + /// + /// Name of the Security Definition + /// + public string SecurityDefinitionName { get; set; } + + /// + /// Scopes of the Security Definition + /// + public string[] SecurityDefinitionScopes { get; set; } + + } +} \ No newline at end of file diff --git a/src/SwaggerWcf/Models/PathAction.cs b/src/SwaggerWcf/Models/PathAction.cs index a3b5f66..a77dc81 100644 --- a/src/SwaggerWcf/Models/PathAction.cs +++ b/src/SwaggerWcf/Models/PathAction.cs @@ -40,7 +40,7 @@ public PathAction() public bool Deprecated { get; set; } - //public List security { get; set; } + public List> Security { get; set; } public void Serialize(JsonWriter writer) { @@ -137,12 +137,38 @@ public void Serialize(JsonWriter writer) } writer.WriteEndArray(); } + if (Deprecated) { writer.WritePropertyName("deprecated"); writer.WriteValue(Deprecated); } + if (Security != null && Security.Any()) + { + writer.WritePropertyName("security"); + writer.WriteStartArray(); + + foreach (var security in Security) + { + writer.WriteStartObject(); + writer.WritePropertyName(security.Key); + writer.WriteStartArray(); + + if (security.Value != null && security.Value.Any()) + { + foreach (var scopename in security.Value) + { + writer.WriteValue(scopename); + } + } + writer.WriteEndArray(); + writer.WriteEndObject(); + } + + writer.WriteEndArray(); + } + writer.WriteEndObject(); } } diff --git a/src/SwaggerWcf/Models/SecurityAuthorization.cs b/src/SwaggerWcf/Models/SecurityAuthorization.cs new file mode 100644 index 0000000..453321e --- /dev/null +++ b/src/SwaggerWcf/Models/SecurityAuthorization.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SwaggerWcf.Models +{ + public class SecurityAuthorization + { + /// + /// (Required) The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". + /// + public string Type { get; set; } + + /// + /// A short description for security scheme. + /// + public string Description { get; set; } + + /// + /// (Required) The name of the header or query parameter to be used. + /// WARNING: Use only, when equals "apiKey" + /// + public string Name { get; set; } + + /// + /// (Required) The location of the API key. Valid values are "query" or "header". + /// WARNING: Use only, when equals "apiKey" + /// + public string In { get; set; } + + /// + /// (Required) The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode". + /// WARNING: Use only, when equals "oauth2" + /// + public string Flow { get; set; } + + /// + /// (Required) The authorization URL to be used for this flow. This SHOULD be in the form of a URL. + /// WARNING: Use only, when equals "oauth2" and equals "implicit" or "accessCode" + /// + public string AuthorizationUrl { get; set; } + + /// + /// (Required) The token URL to be used for this flow. This SHOULD be in the form of a URL. + /// WARNING: Use only, when equals "oauth2" and equals "password" or "application" or "accessCode" + /// + public string TokenUrl { get; set; } + + /// + /// (Required) The available scopes for the OAuth2 security scheme. + /// This maps between a name of a scope to a short description of it (as the value of the property). + /// WARNING: Use only, when equals "oauth2" + /// + public Dictionary Scopes { get; set; } + + public void Serialize(JsonWriter writer) + { + writer.WriteStartObject(); + + if (Type != null) + { + writer.WritePropertyName("type"); + writer.WriteValue(Type); + } + + if (Description != null) + { + writer.WritePropertyName("description"); + writer.WriteValue(Description); + } + + if (Name != null) + { + writer.WritePropertyName("name"); + writer.WriteValue(Name); + } + + if (In != null) + { + writer.WritePropertyName("in"); + writer.WriteValue(In); + } + + if (Flow != null) + { + writer.WritePropertyName("flow"); + writer.WriteValue(Flow); + } + + if (AuthorizationUrl != null) + { + writer.WritePropertyName("authorizationUrl"); + writer.WriteValue(AuthorizationUrl); + } + + if (TokenUrl != null) + { + writer.WritePropertyName("tokenUrl"); + writer.WriteValue(TokenUrl); + } + + if (Scopes != null) + { + writer.WritePropertyName("scopes"); + writer.WriteStartObject(); + + foreach (var scope in Scopes) + { + writer.WritePropertyName(scope.Key); + writer.WriteValue(scope.Value); + } + + writer.WriteEndObject(); + } + + writer.WriteEndObject(); + } + } +} diff --git a/src/SwaggerWcf/Models/SecurityDefinitions.cs b/src/SwaggerWcf/Models/SecurityDefinitions.cs new file mode 100644 index 0000000..1f5b933 --- /dev/null +++ b/src/SwaggerWcf/Models/SecurityDefinitions.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace SwaggerWcf.Models +{ + public class SecurityDefinitions : Dictionary + { + //This is just a wrapper + } +} \ No newline at end of file diff --git a/src/SwaggerWcf/Models/Service.cs b/src/SwaggerWcf/Models/Service.cs index a7e0e0b..70fc9c7 100644 --- a/src/SwaggerWcf/Models/Service.cs +++ b/src/SwaggerWcf/Models/Service.cs @@ -25,6 +25,8 @@ public Service() public List Definitions { get; set; } + public SecurityDefinitions SecurityDefinitions { get; set; } + public void Serialize(JsonWriter writer) { writer.WriteStartObject(); @@ -58,6 +60,13 @@ public void Serialize(JsonWriter writer) writer.WritePropertyName("definitions"); WriteDefinitions(writer); } + + if (SecurityDefinitions != null && SecurityDefinitions.Any()) + { + writer.WritePropertyName("securityDefinitions"); + WriteSecurityDefinitions(writer); + } + writer.WriteEndObject(); } @@ -80,5 +89,16 @@ private void WriteDefinitions(JsonWriter writer) } writer.WriteEndObject(); } + + private void WriteSecurityDefinitions(JsonWriter writer) + { + writer.WriteStartObject(); + foreach (var d in SecurityDefinitions) + { + writer.WritePropertyName(d.Key); + d.Value.Serialize(writer); + } + writer.WriteEndObject(); + } } } diff --git a/src/SwaggerWcf/Support/Mapper.cs b/src/SwaggerWcf/Support/Mapper.cs index 4f76c39..0277bf9 100644 --- a/src/SwaggerWcf/Support/Mapper.cs +++ b/src/SwaggerWcf/Support/Mapper.cs @@ -193,7 +193,8 @@ internal IEnumerable> GetActions(MethodInfo[] targetMe Deprecated = deprecated, OperationId = HttpUtility.HtmlEncode(operationId), ExternalDocs = externalDocs, - Responses = GetResponseCodes(implementation, declaration, wrappedResponse, definitionsTypesList) + Responses = GetResponseCodes(implementation, declaration, wrappedResponse, definitionsTypesList), + Security = GetMethodSecurity(implementation, declaration) // Schemes = TODO: how to get available schemes for this WCF service? (schemes: http/https) }; @@ -661,6 +662,27 @@ private static Type GetTaskInnerType(Type type) return type.BaseType == typeof(Task) ? type.GetGenericArguments()[0] : type; } + private static List> GetMethodSecurity(MethodInfo implementation, MethodInfo declaration) + { + var securityMethodAttributes = implementation.GetCustomAttributes().ToList(); + var securityInterfaceAttributes = declaration.GetCustomAttributes().ToList(); + + var securityAttributes = securityMethodAttributes.Concat(securityInterfaceAttributes).ToList(); + + if (securityAttributes.Any() == false) + return null; + + var security = new List>(); + + foreach (var securityAttribute in securityAttributes) + { + security.Add(new KeyValuePair(securityAttribute.SecurityDefinitionName, securityAttribute.SecurityDefinitionScopes)); + } + + + return security; + } + public static Type GetEnumerableType(Type type) { if (type == null) diff --git a/src/SwaggerWcf/SwaggerWcf.csproj b/src/SwaggerWcf/SwaggerWcf.csproj index 17f48f1..5829711 100644 --- a/src/SwaggerWcf/SwaggerWcf.csproj +++ b/src/SwaggerWcf/SwaggerWcf.csproj @@ -56,11 +56,13 @@ + + @@ -82,6 +84,7 @@ + diff --git a/src/SwaggerWcf/SwaggerWcfEndpoint.cs b/src/SwaggerWcf/SwaggerWcfEndpoint.cs index be6dfda..e3dc140 100644 --- a/src/SwaggerWcf/SwaggerWcfEndpoint.cs +++ b/src/SwaggerWcf/SwaggerWcfEndpoint.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.ServiceModel.Activation; @@ -42,10 +43,11 @@ public static void SetCustomGetFile(GetFileCustomDelegate getFileCustom) Support.StaticContent.GetFileCustom = getFileCustom; } - public static void Configure(Info info) + public static void Configure(Info info, SecurityDefinitions securityDefinitions = null) { Init(); Service.Info = info; + Service.SecurityDefinitions = securityDefinitions; } public Stream GetSwaggerFile() From bcf45a8e1aad14050ab80039080cfdc5fed34968 Mon Sep 17 00:00:00 2001 From: RFehlow Date: Tue, 20 Dec 2016 16:43:44 +0100 Subject: [PATCH 2/3] Fixed issue #55 (Swagger-UI does not expect Html encoded strings in summary section, for description encoding is okay) --- src/SwaggerWcf/Support/Mapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SwaggerWcf/Support/Mapper.cs b/src/SwaggerWcf/Support/Mapper.cs index 0277bf9..d2d292e 100644 --- a/src/SwaggerWcf/Support/Mapper.cs +++ b/src/SwaggerWcf/Support/Mapper.cs @@ -184,7 +184,7 @@ internal IEnumerable> GetActions(MethodInfo[] targetMe PathAction operation = new PathAction { Id = httpMethod.ToLowerInvariant(), - Summary = HttpUtility.HtmlEncode(summary), + Summary = summary, Description = HttpUtility.HtmlEncode(description), Tags = methodTags.Where(t => !t.HideFromSpec).Select(t => HttpUtility.HtmlEncode(t.TagName)).ToList(), From 1e91bd10fef1f5cecace3272add601420cba4785 Mon Sep 17 00:00:00 2001 From: RFehlow Date: Wed, 21 Dec 2016 15:51:52 +0100 Subject: [PATCH 3/3] Removed leading whitespaces in readme --- README.md | 143 +++++++++++++++++++++++++++--------------------------- 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index a6b169b..55d2c39 100644 --- a/README.md +++ b/README.md @@ -141,95 +141,94 @@ var security = new SecurityDefinitions }; SwaggerWcfEndpoint.Configure(info, security); - ``` - +``` +### Step 5: Decorate WCF services interfaces - ### Step 5: Decorate WCF services interfaces +For each method, configure the `WebInvoke` or `WebGet` attribute, and add a `SwaggerWcfPath` attribute. - For each method, configure the `WebInvoke` or `WebGet` attribute, and add a `SwaggerWcfPath` attribute. +```csharp - ```csharp +[ServiceContract] +public interface IStore +{ + [SwaggerWcfPath("Create book", "Create a book on the store")] + [WebInvoke(UriTemplate = "/books", + BodyStyle = WebMessageBodyStyle.Bare, + Method = "POST", + RequestFormat = WebMessageFormat.Json, + ResponseFormat = WebMessageFormat.Json)] + [OperationContract] + Book CreateBook(Book value); + + // [.......] +} - [ServiceContract] - public interface IStore - { - [SwaggerWcfPath("Create book", "Create a book on the store")] - [WebInvoke(UriTemplate = "/books", - BodyStyle = WebMessageBodyStyle.Bare, - Method = "POST", - RequestFormat = WebMessageFormat.Json, - ResponseFormat = WebMessageFormat.Json)] - [OperationContract] - Book CreateBook(Book value); - - // [.......] - } +``` - ``` +### Step 6: Decorate WCF services class - ### Step 6: Decorate WCF services class +Add the `SwaggerWcf` and `AspNetCompatibilityRequirements` attributes to the class providing the base path for the service (the same as used in step 2). +Optinally, for each method, add the `SwaggerWcfTag` to categorize the method and the `SwaggerWcfResponse` for each possible response from the service. - Add the `SwaggerWcf` and `AspNetCompatibilityRequirements` attributes to the class providing the base path for the service (the same as used in step 2). - Optinally, for each method, add the `SwaggerWcfTag` to categorize the method and the `SwaggerWcfResponse` for each possible response from the service. +```csharp - ```csharp +[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] +[SwaggerWcf("/v1/rest")] +public class BookStore : IStore +{ + [SwaggerWcfTag("Books")] + [SwaggerWcfResponse(HttpStatusCode.Created, "Book created, value in the response body with id updated")] + [SwaggerWcfResponse(HttpStatusCode.BadRequest, "Bad request", true)] + [SwaggerWcfResponse(HttpStatusCode.InternalServerError, + "Internal error (can be forced using ERROR_500 as book title)", true)] + public Book CreateBook(Book value) + { + // [.......] + } + + // [.......] +} - [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] - [SwaggerWcf("/v1/rest")] - public class BookStore : IStore - { - [SwaggerWcfTag("Books")] - [SwaggerWcfResponse(HttpStatusCode.Created, "Book created, value in the response body with id updated")] - [SwaggerWcfResponse(HttpStatusCode.BadRequest, "Bad request", true)] - [SwaggerWcfResponse(HttpStatusCode.InternalServerError, - "Internal error (can be forced using ERROR_500 as book title)", true)] - public Book CreateBook(Book value) - { - // [.......] - } +``` - // [.......] - } +### Step 7: Decorate data types used in WCF services - ``` +```csharp - ### Step 7: Decorate data types used in WCF services +[DataContract(Name = "book")] +[Description("Book with title, first publish date, author and language")] +[SwaggerWcfDefinition(ExternalDocsUrl = "http://en.wikipedia.org/wiki/Book", ExternalDocsDescription = "Description of a book")] +public class Book +{ + [DataMember(Name = "id")] + [Description("Book ID")] + public string Id { get; set; } - ```csharp + // [.......] +} - [DataContract(Name = "book")] - [Description("Book with title, first publish date, author and language")] - [SwaggerWcfDefinition(ExternalDocsUrl = "http://en.wikipedia.org/wiki/Book", ExternalDocsDescription = "Description of a book")] - public class Book - { - [DataMember(Name = "id")] - [Description("Book ID")] - public string Id { get; set; } +``` - // [.......] - } +Note: make sure you add at least the `DataContract` and `DataMember` attributes in classes and properties + +## Attributes + +| Attribute | Used in | Description | Options | +| ---------------------- |------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------- | +| `SwaggerWcf` | `Class`, `Interface` | Enable parsing WCF service | `ServicePath` | +| `SwaggerWcfHidden` | `Class`, `Method`, `Property`, `Parameter` | Hide element from Swagger | | +| `SwaggerWcfTag` | `Class`, `Method`, `Property`, `Parameter` | Add a tag to an element | `TagName`, `HideFromSpec` | +| `SwaggerWcfHeader` | `Method` | Configure method HTTP headers | `Name`, `Required`, `Description`, `DefaultValue` | +| `SwaggerWcfPath` | `Method` | Configure a method in Swagger | `Summary`, `Description`, `OperationId`, `ExternalDocsDescription`, `ExternalDocsUrl`, `Deprecated` | +| `SwaggerWcfParameter` | `Parameter` | Configure method parameters | `Required`, `Description` | +| `SwaggerWcfProperty` | `Property` | Configure property parameters | `Required`, `Description`, `Minimum`, `Maximum`, `Default`, ... | +| `SwaggerWcfResponse` | `Method` | Configure method return value | `Code`, `Description`, `EmptyResponseOverride`, `Headers` | +| `SwaggerWcfDefinition` | `Class` | Configure a data type | `ExternalDocsDescription`, `ExternalDocsUrl` | +| `SwaggerWcfReturnType` | `Method` | Override method return type | `ReturnType` | +| `SwaggerWcfContentTypes` | `Method` | Override consume/produce content-types | `ConsumeTypes`, `ProduceTypes` | +| `SwaggerWcfSecurity` | `Method` | Add security background to this method | `SecurityDefinitionName`, `params Scopes` | - ``` - - Note: make sure you add at least the `DataContract` and `DataMember` attributes in classes and properties - - ## Attributes - - | Attribute | Used in | Description | Options | - | ---------------------- |------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------- | - | `SwaggerWcf` | `Class`, `Interface` | Enable parsing WCF service | `ServicePath` | - | `SwaggerWcfHidden` | `Class`, `Method`, `Property`, `Parameter` | Hide element from Swagger | | - | `SwaggerWcfTag` | `Class`, `Method`, `Property`, `Parameter` | Add a tag to an element | `TagName`, `HideFromSpec` | - | `SwaggerWcfHeader` | `Method` | Configure method HTTP headers | `Name`, `Required`, `Description`, `DefaultValue` | - | `SwaggerWcfPath` | `Method` | Configure a method in Swagger | `Summary`, `Description`, `OperationId`, `ExternalDocsDescription`, `ExternalDocsUrl`, `Deprecated` | - | `SwaggerWcfParameter` | `Parameter` | Configure method parameters | `Required`, `Description` | - | `SwaggerWcfProperty` | `Property` | Configure property parameters | `Required`, `Description`, `Minimum`, `Maximum`, `Default`, ... | - | `SwaggerWcfResponse` | `Method` | Configure method return value | `Code`, `Description`, `EmptyResponseOverride`, `Headers` | - | `SwaggerWcfDefinition` | `Class` | Configure a data type | `ExternalDocsDescription`, `ExternalDocsUrl` | - | `SwaggerWcfReturnType` | `Method` | Override method return type | `ReturnType` | - | `SwaggerWcfContentTypes` | `Method` | Override consume/produce content-types | `ConsumeTypes`, `ProduceTypes` | - | `SwaggerWcfSecurity` | `Method` | Add security background to this method | `SecurityDefinitionName`, `params Scopes`| ## Tags