A plugin for ServiceStack that generates a series of POCOs documenting all available services as well as request and response DTOs. These POCOs will allow the data to be be visualised in a number of standard API documentation formats (e.g. postman, swagger, RAML).
The plugin uses introspection on a number of different sources to generate as rich a set of documentation POCOs as possible, containing request/response types, status codes, constraints etc.
When the service starts up, this plugin will generate documentation that is accessible via service endpoints.
Install the package https://www.nuget.org/packages/ServiceStack.IntroSpec
PM> Install-Package ServiceStack.IntroSpec
The plugin is added like any other. It has a dependency on ServiceStack's Metadata Plugin.
The plugin configuration requires values for the following: ContactEmail
,
ContactName
and Description
.
There are two methods of loading the plugin configuration:
This method uses the IAppSettings
implementation registered in the AppHost to read any configuration values. If a custom IAppSettings
implementation is to be used it must be registered prior to instantiating the plugin.
<appSettings>
<!-- Required -->
<add key="servicestack.plugins.introSpec.contact.name" value="Private Pile"/>
<add key="servicestack.plugins.introSpec.contact.email" value="private@pile.com"/>
<add key="servicestack.plugins.introSpec.description" value="This is my service, there are many just like it, but this one is mine"/>
<!-- Optional -->
<add key="servicestack.plugins.introSpec.contact.url" value="http://socialnetwork.com/profile/pile"/>
<add key="servicestack.plugins.introSpec.licenseurl" value="https://opensource.org/licenses/MPL-2.0"/>
</appSettings>
public override void Configure(Container container)
{
Plugins.Add(new IntroSpecFeature()));
}
This method can be used in conjunction with the above AppSettings
approach - any values explicitly set will be used and AppSettings
used as fallback.
public override void Configure(Container container)
{
Plugins.Add(new IntroSpecFeature
{
Description = "Desc will override anything in appSettings",
LicenseUrl = new Uri("http://mozilla.org/MPL/2.0/"),
ContactName = "Joe Bloggs",
ContactEmail = "email@address.com"
}));
}
Although supported this method is now marked as obsolete to bring plugin into line with other ServiceStack plugins. The feature has been renamed to IntroSpecFeature
from ApiSpecFeature
.
public override void Configure(Container container)
{
// Register plugin
Plugins.Add(new ApiSpecFeature(config =>
config.WithDescription("This is a demo app host for testing.")
.WithLicenseUrl(new Uri("http://mozilla.org/MPL/2.0/"))
.WithContactName("Joe Bloggs")
.WithContactEmail("email@address.com")));
}
There is a DocumenterSettings
class that can be used to configure certain behaviours and provide fallback values.
See Settings for more details of these options.
The plugin uses the Metadata Plugin
as the seed for all operations, then uses a series of 'enrichers' to generate documentation, these are called in the order listed below.
The general approach is to use Reflection to get as much information as possible which can then be augmented (with descriptions, notes etc) from further sources.
For full details of the sources of data for the various enrichers please see Sources
The ReflectionEnricher
uses reflection to generate documentation. This is the best source of information as it uses many of the same mechanisms as ServiceStack to determine information.
For example the following class would look at a combination of [Api]
, [ApiResponse]
, [Route]
, [ApiMember]
, [IgnoreDataMember]
and [ApiAllowableValues]
attributes to generate the documentation.
[Api("Demo Request Description")]
[ApiResponse(HttpStatusCode.OK, "Everything is hunky dory")]
[ApiResponse(HttpStatusCode.InternalServerError, "Something went wrong")]
[Route("/request/{Name}", "GET,POST", Summary = "Route summary", Notes = "Notes about request")]
public class DemoRequest : IReturn<DemoResponse>
{
[ApiMember(Name = "Name parameter", Description = "This is a description of name", ParameterType = "body", DataType = "string", IsRequired = true)]
[ApiAllowableValues("Name", typeof(NameEnum))]
public string Name { get; set; }
[IgnoreDataMember]
public string Ignored { get; set; }
[ApiMember(ExcludeInSchema = true)]
public int Optional { get; set; }
}
[Api("Demo Response Description")]
public class DemoResponse
{
[ApiMember(Name="Response Message", Description = "The returned message")]
public string Message { get; set; }
}
This approach uses a combination of attributes, DTO, service types and any implemented interfaces to generate a good description of a service.
This uses an approach similar to FluentValidation to provide additional information about objects and avoid decorating DTO's with a lot of attributes needed only for metadata, documentation or specification generation.
The enricher scans for all implementations of AbstractRequestSpec<T>
(for Request DTOs) AbstractTypeSpec<T>
(any other classes to be documented, e.g. embedded classes or Response DTOs) and generates documentation based on this. E.g.
public class DemoRequestSpec : AbstractRequestSpec<DemoRequest>
{
public DemoRequestAbstract()
{
Title = "Plain request title from abstract";
Description = "Demo Request Description";
Notes = "Notes about demo request";
Category = "Category1";
AddTags("Tag1", "Tag2", "Tag3");
AddStatusCodes(HttpVerbs.Post,
new StatusCode
{
Code = 500,
Name = "Internal Server Error",
Description = "Something went wrong"
},
(StatusCode)HttpStatusCode.OK);
AddContentTypes(HttpVerbs.Get, "application/x-custom-type");
For(t => t.Name)
.With(p => p.Title, "Name parameter")
.With(p => p.IsRequired, true)
.With(p => p.Description, "This is a description of name.");
For(t => t.Age)
.With(p => p.Title, "This is optional.")
.With(p => p.IsRequired, false);
}
}
public class DemoResponseSpec : AbstractTypeSpec<DemoResponse>
{
public DemoResponseSpec()
{
Title = "Demo response title from documenter";
Description = "Demo Response Description";
For(t => t.Message)
.With(p => p.Title, "Response Message")
.With(p => p.Description, "The message returned from service.");
}
}
This approach allows for very explicit setting of properties and separates documentation concerns from your DTO's.
Whilst they will have no effect to the processing of requests it provides the ability to generate rich documentation about DTOs.
This uses the standard C# Xml Documentation Comments to generate documentation.
/// <summary>
/// Demo Request Description
/// </summary>
/// <remarks>Notes about request</remarks>
public class DemoRequest : IReturn<DemoResponse>
{
/// <summary>Name parameter</summary>
/// <remarks>This is a description of name</remarks>
public string Name { get; set; }
public string Ignored { get; set; }
/// <summary>Age Parameter</summary>
public int Age{ get; set; }
}
/// <summary>
/// Demo Response Description
/// </summary>
public class DemoResponse
{
/// <summary>Response Message</summary>
/// <remarks>The returned message</remarks>
public string Message { get; set; }
}
The XML documentation comments are for general documentation about classes and not specifically for documentating APIs and DTOs but if need be these values can be used.
Note: for this to work the XML documentation file must be generated for the service. To do so RMC project -> Properties -> Build -> check "XML documentation file" box.
This will use global settings within the DocumenterSetting
object for setting both fallback values (e.g. .FallbackNotes
for if no other Notes
are found) or default values (e.g. .DefaultTags
which are combined with tags from other sources).
DocumenterSettings.FallbackNotes = "Default notes";
DocumenterSettings.FallbackCategory = "Fallback Category";
DocumenterSettings.DefaultTags = new[] { "DefaultTag" };
DocumenterSettings.DefaultStatusCodes = new List<StatusCode>
{
((StatusCode)429).WithDescription("This is rate limited"),
};
Plugins.Add(new IntroSpecFeature());
The .With()
method can be used to set multiple values:
DocumenterSettings.With(fallbackNotes: "Default notes",
fallbackCategory: "Fallback Category",
defaultTags: new[] { "DefaultTag" },
defaultStatusCodes: new List<StatusCode>{
((StatusCode) 429).WithDescription("This is rate limited")
});
Plugins.Add(new IntroSpecFeature());
The plugin filters the Metadata.OperationsMap
to get a list of Operation
objects that contain the requests to be documented. This filter can be customised by providing a predicate to the plugin using the IntroSpecFeature.WithOperationsFilter()
method. The default filter excludes any types that have [Exclude(Feature.Metadata]
or [Exclude(Feature.ServiceDiscovery]
or any restrictions.
3 services are registered as part of this plugin.
A demo site running this plugin is available at http://introspec.servicestack.net/.
The plugin will also register a service which can be accessed at /spec
to view the raw generated documentation POCOs.
This endpoint can optionally be filtered by ?requestDto
, ?tag
and/or ?category
.
Example output is:
{
"ApiDocumentation": {
"Title": "DemoDocumentationService",
"ApiVersion": "2.0",
"ApiBaseUrl": "http://127.0.0.1:8090/",
"Description": "This is a demo app host setup for testing documentation.",
"LicenceUrl": "http://mozilla.org/MPL/2.0/",
"Contact": {
"Name": "Joe Bloggs",
"Email": "email@address.com"
},
"Resources": [
{
"TypeName": "FallbackRequest",
"Title": "Fallback request title",
"Description": "Fallback request desc",
"Properties": [
{
"Id": "Name",
"ClrType": "System.String, mscorlib",
"Title": "Name parameter abstract class definition",
"IsRequired": true
},
{
"Id": "Age",
"ClrType": "System.Int32, mscorlib",
"Title": "Age is optional",
"IsRequired": false,
"Contraints": {
"Name": "Age Range",
"Min": 0,
"Max": 120,
"Type": "Range"
}
}
],
"Actions": [
{
"Verb": "GET",
"Notes": "This is a note about GET route",
"StatusCodes": [
{
"Code": 429,
"Description": "This is rate limited",
"Name": "Too Many Requests"
},
{
"Code": 200,
"Name": "OK"
}
],
"ContentTypes": [
"application/xml",
"application/json"
],
"RelativePaths": [
"/fallback"
]
}
],
"ReturnType": {
"Title": "ComplexResponse"
"Properties": [
{
"Id": "Message",
"ClrType": "System.String, mscorlib",
"Title": "Message",
"Description": "The returned message"
}
]
},
"Category": "Category1",
"Tags": [
"Tag1",
"DefaultTag"
]
}
]
}
}
The plugin will also register a service which can be accessed at /spec/summary
to view the raw generated documentation POCOs. This will return some metadata about the documentation.
The values that are returned are the 3 fields that can be used to filter the other 2 services: Request DTO Name, Tags and Category. E.g.
{
"DtoNames": [
"DemoRequest",
"FallbackRequest",
"EmptyDtoRequest",
"OneWayRequest",
"SecureRequest"
],
"Categories": [
"Fallback Category",
"Category1"
],
"Tags": [
"DefaultTag",
"Tag1",
"Tag2",
"Tag3"
]
}
In future this will be used to power a separate application that can aggregate documentation from multiple different services.
The 3rd service that is registered can be found at /spec/postman
and generates JSON that can be consumed by the Postman REST client.
Although there is an existing Postman Plugin for ServiceStack which does the same thing, Postman was the easiest format to output the specification information as.
This endpoint can optionally be filtered by ?requestDto
, ?tag
and/or ?category
.
The plugin currently does not respect AuthenticateAttribute
, it will document them but not restrict visibility or access.