Lets say your Sitecore solution needs to have some API services. The obvious thing is to implement them using .net Web API. Sometimes the understanding of various methods of an API can be a challenge for a developer when building a consuming app. Swagger is a machine-readable representation of a RESTful API that enables support for interactive documentation, client SDK generation, and discoverability.

Using Swagger

Generating good documentation and help pages for your Web API, using Swagger is as easy as adding a NuGet package.

As long as Sitecore is still an asp.net application, we can use Swagger with it! So I implement a Web API controller that I want to host it within my Sitecore instance and to document it somehow:

namespace Feature.WebApi.Controllers
{
	using System.Web.Http;

	[RoutePrefix("-/api/v1")]
	public class MyServicesApiController : ApiController
	{
		[HttpGet]
		[Route("hello-world")]
		public IHttpActionResult HelloWorld()
		{
			return this.Ok("Hello world");
		}
	}
}

Okay, this is my “hello world” API method. Now I will install Swagger to my Feature.Swagger project using nuget:

install-package Swashbuckle

The default nuget package will create a startup config at /App_Start/SwaggerConfig.cs. By default it uses WebActivator extensions to bind it to the application start so it executes the configuration when your web application is being initialized.

By default it won’t run smoothly with Sitecore, but we can make it to work properly with few configurations. The entry point to Swagger UI would be {your.domain.com}/swagger. I run it and see next picture:

swagger-error1

In order to resolve it, we need to modify /App_Start/SwaggerConfig.cs and un-comment next line:

c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());

Run it again – and we see next issue:

swagger-error-schemas-conflict

This time we need to un-comment another line of config:

c.UseFullTypeNameInSchemaIds();

Ok, now its up and running, however there is one annoing error in the bottom which tels that there is a schema validation errors. The Swagger UI still works, but we don’t really want to see any error messages right? We can bypass it by un-commenting one more line of config:

c.DisableValidator();

swagger-services-mixed

Web API services

Now it looks up and running, and I am even able to find my service method. However, Sitecore has a lot of Web API services that it uses itself and its quite hard to find my own service among others. It would be nice to separate Sitecore native Web API controllers from mine. It supports API versioning, and from my perspective, this is one of ways how we can separate that. We can add a method to SwaggerConfig which will declare versions of API.

I will call Sitecore services as version “sc” and my own as “my_services“:

private static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion)
{
	if (apiDesc.Route.RouteTemplate.StartsWith("-/api/v1") && targetApiVersion == "my_services") return true;
	if (apiDesc.Route.RouteTemplate.StartsWith("sitecore") && targetApiVersion == "sc") return true;
	return false;
}

And in the config we need to comment/remove this line

c.SingleApiVersion("v1", "Feature.Swagger");

And add this:

c.MultipleApiVersions(
	ResolveVersionSupportByRouteConstraint,
	vc =>
	{
		vc.Version("my_services", "My services API (you can switch to Sitecore API /swagger/docs/sc)");
		vc.Version("sc", "Sitecore services (you can switch to My services API /swagger/docs/my_services)");
	});

So now we have 2 separated views for Sitecore API and My API. They are treated as API versions by Swagger UI:

swagger-my-services

swagger-sitecore-services

Installation

Giving that documentation publicly, perhaps, is not a very good idea as it creates a security issue. So what we need to do is to hide Swagger UI behind a login and make it available only for Sitecore administrator users. We can re-use Sitecore’s defaul login page for this. From my point of view, the best way of doing it is using HTTP MessageHandlers. We can implement next message handler:

namespace Feature.Swagger
{
	using System;
	using System.Net;
	using System.Net.Http;
	using System.Threading;
	using System.Threading.Tasks;
	using Sitecore;

	public class SwaggerSitecoreAuthMessageHandler : DelegatingHandler
	{
		protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
		{
			var isAuthenticated = Context.User?.IsAuthenticated ?? false;
			var isAdministrator = Context.User?.IsAdministrator ?? false;

			if (this.IsSwaggerRequest(request) && (!isAuthenticated || !isAdministrator))
			{
				var response = new HttpResponseMessage(HttpStatusCode.Redirect);
				var uri = request.RequestUri;
				var currentOrigin = uri.AbsoluteUri.Replace(uri.PathAndQuery, string.Empty);
				var logiPage = currentOrigin + @"/sitecore/login?returnurl=/swagger";
				response.Headers.Location = new Uri(logiPage);
				return Task.FromResult(response);
			}
			else
			{
				return base.SendAsync(request, cancellationToken);
			}
		}

		private bool IsSwaggerRequest(HttpRequestMessage request)
		{
			return request.RequestUri.PathAndQuery.ToLowerInvariant().StartsWith("/swagger");
		}
	}
}

And then add a line in SwaggerConfig class like this:

GlobalConfiguration.Configuration.MessageHandlers.Add(new SwaggerSitecoreAuthMessageHandler());

Now we have successfully installed Swagger to a Sitecore solution. However, as I mentioned before, it uses WebActivator extensions, which is more like anti-pattern for web solutions and I would better get rid of it in order to keep helix principles in place. Instead, we can make it more Sitecore native way – run the config on Sitecore initialize pipeline.

So first we need to do is to remove this line:

[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]

And then create a super-simple initialize pipeline processor that runs the initialization:

namespace Feature.Swagger.Pipelines.Initialize
{
	using Sitecore.Pipelines;

	public class InitializeSwagger
	{
		public void Process(PipelineArgs args)
		{
			SwaggerConfig.Register();
		}
	}
}

Of course we need to add that to a configuration file /App_Config/Include/Feature/Feature.Swagger.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="Feature.Swagger.Pipelines.Initialize.InitializeSwagger, Feature.Swagger"/>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

Lastly, my full SwaggerConfig.cs file looks like this:

namespace Feature.Swagger
{
	using System.Web.Http;
	using System.Linq;
	using Swashbuckle.Application;
	using System.Web.Http.Description;

	public class SwaggerConfig
    {
        public static void Register()
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

	        GlobalConfiguration.Configuration.MessageHandlers.Add(new SwaggerSitecoreAuthMessageHandler());

			GlobalConfiguration.Configuration
                .EnableSwagger(c =>
                    {
						c.MultipleApiVersions(
							ResolveVersionSupportByRouteConstraint,
							vc =>
							{
								vc.Version("my_services", "My services API (you can switch to Sitecore API /swagger/docs/sc)");
								vc.Version("sc", "Sitecore services (you can switch to My services API /swagger/docs/my_services)");
							});

						c.UseFullTypeNameInSchemaIds();

                        c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
                    })
                .EnableSwaggerUi(c =>
                    {
                        c.DisableValidator();
                    });
        }

	    private static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion)
	    {
		    if (apiDesc.Route.RouteTemplate.StartsWith("-/api/v1") && targetApiVersion == "my_services") return true;
		    if (apiDesc.Route.RouteTemplate.StartsWith("sitecore") && targetApiVersion == "sc") return true;
		    return false;
	    }
	}
}

This is it. It’s integrated with our Sitecore solution. Read more about all Swagger capabilities at their website https://swagger.io

I have been using Swagger with Sitecore 8.x and Sitecore 9 versions, it all works! See our GitHub repo here.

Share article
See also

MVC renderings with xWrap framework – Sitecore Experience Wrapper

Read more Volodymyr Hil 10.12.2018

Introducing xWrap framework – Sitecore Experience Wrapper

Read more Volodymyr Hil 09.12.2018

A recap from Sitecore Symposium 2018 and MVP summit

Read more Volodymyr Hil 25.10.2018

Using the service bus to transfer messages between instance roles

Read more Volodymyr Hil 10.06.2018

Rebuild Sitecore Analytics Index without rebuilding reporting database

Read more Volodymyr Hil 16.05.2017