notes from a passionate developer





This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

ASP.Net Core and a simple discovery endpoint

I'm currently building a simple solution that will have a few simple web APIs built using ASP.Net Core. The APIs are not going to be fully fledged REST APIs according to Richardson's maturity model. So there will not be any HATEOAS with resource linking etc. Each API will most likely have one or two endpoints. I want the root endpoint to provide some simple discovery info and not extensive Swagger enabled API docs but info like: deployed version, current time and available routes. This endpoint is used for simple discovery purposes and checking if a service is alive with e.g. simple smoke tests after deploy etc.

Please remember. This is very early sample code and just something for you to get inspired by.

Sample result

    "version": "",
    "time": "2017-10-29T09:27:50.5469861Z",
    "routes": [
            "name": "UploadFile",
            "template": "Uploads/{year:range(2000, 2100)}/{month:range(1, 12)}/{context:minlength(2)}",
            "httpMethods": [


Even though this creates coupling between my services, in the form of shared infrastructure code, I've decided to expose this as a middle-ware hook:

app.MapWhen(ctx => ctx.Request.Path == "/", c => c.UseApiInfo());

The middle-ware is very simple. It will only support JSON as output. I could have used ObjectResult and used content-negotiation, but my design decision for these internal services has been to just use JSON for it.

public static class ApiInfoMiddlewareExtensions
    public static IApplicationBuilder UseApiInfo(this IApplicationBuilder builder)
        => builder.UseMiddleware<ApiInfoMiddleware>();

public class ApiInfoMiddleware
    private readonly IApiInfoProvider _provider;
    private readonly JsonSerializerSettings _jsonSerializerSettings;

    public ApiInfoMiddleware(
        RequestDelegate _,
        IApiInfoProvider provider,
        JsonSerializerSettings jsonSerializerSettings)
        _provider = provider;
        _jsonSerializerSettings = jsonSerializerSettings;

    public async Task Invoke(HttpContext httpContext)
        var response = _provider.Get();

        httpContext.Response.ContentType = "application/json";
        await httpContext.Response.WriteAsync(
            JsonConvert.SerializeObject(response, _jsonSerializerSettings));

By specifying the dependencies in the constructor I get support for the wired up dependency injection. This will be a terminating middle-ware, hence why next is not being used.


There's one simple implementation of an IApiInfoProvider, that produces a simple response that the middle-ware just serializes to JSON.

public class ApiInfoProvider : IApiInfoProvider
    private readonly ApiVersion _version;
    private readonly Func<DateTime> _timeFn;
    private readonly Lazy<IRouteInfo[]> _routeInfoFn;

    public ApiInfoProvider(ApiVersion version, IClock clock, IRouteInfoProvider routeInfoProvider)
        _version = version;
        _timeFn = () => clock.Now;
        _routeInfoFn = new Lazy<IRouteInfo[]>(routeInfoProvider.Get);

    public IApiInfo Get() => new ApiInfo(

    private class ApiInfo : IApiInfo
        public string Version { get; }
        public DateTime Time { get; }
        public IRouteInfo[] Routes { get; }

        public ApiInfo(string version, DateTime time, IRouteInfo[] routes)
            Version = version;
            Time = time;
            Routes = routes;
public class ApiVersion
    private readonly string _version;

    public ApiVersion(string version)
        _version = version;

    public static implicit operator string(ApiVersion v) => v.ToString();

    public override string ToString() => _version;

The dependencies are registered in the IServiceCollection:



services.AddSingleton<IRouteInfoProvider, RouteInfoProvider>();

services.AddSingleton(new ApiVersion(

services.AddSingleton<IApiInfoProvider, ApiInfoProvider>();


The final piece worth looking at is the provider that extracts the routes.

public class RouteInfoProvider : IRouteInfoProvider
    private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;

    public RouteInfoProvider(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
        _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;

    public IRouteInfo[] Get() => _actionDescriptorCollectionProvider
        .Where(ad => ad.AttributeRouteInfo != null)

    private class RouteInfo : IRouteInfo
        public string Name { get; private set; }
        public string Template { get; private set; }
        public string[] HttpMethods { get; private set; }

        internal static IRouteInfo Create(ActionDescriptor ad) => new RouteInfo
            Name = ad.AttributeRouteInfo.Name,
            Template = ad.AttributeRouteInfo.Template,
            HttpMethods = ad
                .SelectMany(c => c.HttpMethods)

That's it. All wrapped up in some simple extension methods used by my APIs in their respective Startup classes.

public class Startup
    public void ConfigureServices(IServiceCollection services) => services
        .AddMvcApi(GetType().Assembly); //Extension in custom NuGet

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) => app
        .ConfigureMvcApi(env, loggerFactory); /Extension in custom NuGet

That's it. Hope you can use it to something.


View Comments