If you aren’t using Swagger/Swashbuckle on your WebAPI project, you may have been living under a rock, if so go out and download it now 🙂
Its a port from a node.js project that rocks! And MS is really getting behind in a big way. If you haven’t heard of it before, imagine WSDL for REST with a snazy Web UI for testing.
Swagger is relatively straight forward to setup with WebAPI, however there were a few gotchas that I ran into that I thought I would blog about.
The first one we ran into is so common MS have a blog post about it. This issue deals with an exception you’ll get logged due to the way swashbuckle auto generates the ID from the method names.
A common example is when you have methods like the following:
GET /api/Company // Returns all companies
GET /api/Company/{id} // Returns company of given ID
In this case the swagger IDs will both be “Company_Get”, and the generation of the swagger json content will work, but if you try to run autorest or swagger-codegen on this they will fail.
The solution is to create a custom attribute to apply to the methods like so
// Attribute namespace MyCompany.MyProject.Attributes { [AttributeUsage(AttributeTargets.Method)] public sealed class SwaggerOperationAttribute : Attribute { public SwaggerOperationAttribute(string operationId) { this.OperationId = operationId; } public string OperationId { get; private set; } } } //Filter namespace MyCompany.MyProject.Filters { public class SwaggerOperationNameFilter : IOperationFilter { public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) { operation.operationId = apiDescription.ActionDescriptor .GetCustomAttributes<SwaggerOperationAttribute>() .Select(a => a.OperationId) .FirstOrDefault(); } } } //SwaggerConfig.cs file namespace MyCompany.MyProject { public class SwaggerConfig { private static string GetXmlCommentsPath() { return string.Format(@"{0}\MyCompany.MyProject.XML", System.AppDomain.CurrentDomain.BaseDirectory); } public static void Register() { var thisAssembly = typeof(SwaggerConfig).Assembly; GlobalConfiguration.Configuration .EnableSwagger(c => { c.OperationFilter<SwaggerOperationNameFilter>(); c.IncludeXmlComments(GetXmlCommentsPath()); // the above is for comments doco that i will talk about next. // there will be a LOT of additional code here that I have omitted } } } }
Then apply like this:
[Attributes.SwaggerOperation("CompanyGetOne")] [Route("api/Company/{Id}")] [HttpGet] public Company CompanyGet(int id) { // code here } [Attributes.SwaggerOperation("CompanyGetAll")] [Route("api/Company")] [HttpGet] public List<Company> CompanyGet() { // code here }
Also mentioned I the MS article is XML code comments, these are awesome for documentation, but make sure you don’t have any potty mouth programmers
This is pretty straight forward, see the setting below
The issue we had though was packaging them with octopus as it’s an output file that is generated at build time. We use the octopack nuget package to wrap up our web projects, so in order to package build-time output (other than bin folder content) we need to create a nuspec file in the project. Octopack will default to using this instead of the csproj file if it has the same name.
e.g. if you project is called MyCompany.Myproject.csproj, create a nuspec file in this project called MyCompany.MyProject.nuspec.
Once you add a file tag into the nuspec file this will override octopack ebnhaviour of looking up the csproj file for files, but you can override this behavior by using this msbuild switch.
/p:OctoPackEnforceAddingFiles=true
This will make octopack package files from the csproj first, then use what is specified in the files tag in the nuspec file as additional files.
So our files tag just specifies the MyCompany.MyProject.XML file, and we are away and deploying comments as doco!
We used to use sandcastle so most of the main code comment doco marries up between the two.
Autofac DI is a bit odd with the WebAPI controllers, we generally use DI on the constructor params, but WebAPI controllers require a parameter-less constructor. So we need to use Properties for DI. This is pretty straight forward you juat need to call the PropertiesAutowired method when registering them. And as well with the filters and Attributes. In our example below I put my filters in a “Filters” Folder/Namespace, and my Attributes in an “Attributes” Folder/Namespace
// this code goes in your Application_Start var containerBuilder = new ContainerBuilder(); &nbsp; containerBuilder.RegisterAssemblyTypes(typeof(WebApiApplication).Assembly) .Where(t => t.IsInNamespace("MyCompany.MyProject.Attributes")).PropertiesAutowired(); containerBuilder.RegisterAssemblyTypes(typeof(WebApiApplication).Assembly) .Where(t => t.IsInNamespace("MyCompany.MyProject.Filters")).PropertiesAutowired(); containerBuilder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(); containerBuilder.RegisterWebApiFilterProvider(config);