- Swagger documentation with OData
01/18/2021 - Blazor PWA with AAD Authentication
06/01/2020 - Azure Durable Functions
02/22/2019 - Going Serverless with Azure Functions
11/05/2017 - Automated UI Testing in MVC
02/12/2015 - SharePoint web.config Modification
05/23/2012 - SharePoint 2010 Client Object Model Part 1
10/13/2011 - SharePoint 2010 Client Object Model Part 2
10/13/2011 - In-place Editing with ASP.NET Repeater
08/29/2011 - Custom SharePoint List Forms
07/11/2011 - SharePoint 2010 Taxonomy Import
05/09/2011 - SharePoint Custom Provisioning Provider
04/21/2011 - USING SPMetal
03/14/2011 - SharePoint 2010 Site Actions Menu
11/14/2010 - Import List VS 2010 SharePoint Extension
09/24/2010 - Windows Azure Storage
05/24/2010 - Deploy and use SPCopy Part 2
04/27/2010 - Using SPExport and SPImport Part 1
04/13/2010 - Entity Framework Performance
10/31/2009 - WSS List and Database Synchronization
11/30/2008 - ASP.NET MVC Part2
06/05/2008 - ASP.NET MVC Part 1
03/23/2008 - LINQ to XML
03/14/2008 - Hierarchical Exception Handling with the Enterprise Library
10/06/2007 - Introduction to the Validation Application Block
02/02/2007 - CSS Variables
11/17/2006 - Custom XPath Functions
08/28/2006 - Using SqlDependency for data change events
11/18/2005 - Application Suite Template
02/10/2004 - ExpandingTable with Design-Time support
12/13/2003 - DataGridDemo
06/15/2002 - C# Album Viewer
04/12/2002 - Screen Scraping with C# for ASP.NET
03/01/2002 - XML TIming
11/03/2001 - Using XML, XSL, DHTML and Javascript in your Windows applications
06/24/2001
Swagger documentation with OData
1/18/2021
OData and Swagger are two well-known technologies that separately can provide enhanced capabilities and documentation to your project and are easily implemented with a few lines of code. Getting them to work together, however, takes a little more setup and configuration. I will not be covering the depths of Swagger or OData here; there are a plethora existing articles covering these. Rather, this article will look at how to get them to work together.
What is Swagger
Swagger, also known as Open API, is a standard for documenting REST APIs. It doesn't implement documentation but rather defines how the documentation should appear. Implementation is via tools, such as Swashbuckle, which is integrated into ASP.NET projects. When creating a new project it is easy to implement by clicking the checkbox.

This code will be added to the Startup class in the ConfigureServices and Configure methods respectively.
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "SwaggerOdata", Version = "v1" }); });
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "SwaggerOdata v1")); }

What is OData?
OData, or Open Data Protocol, is a set of best practices for building and consuming REST APIs. Again, I won't be going into depth on OData, but briefly, lets say we have a model such as this
public class TodoItem { public int ID { get; set; } public string Name{ get; set; } public bool IsComplete { get; set; } public DateTime Created { get; set; } public DateTime? Completed { get; set; } }
and a method on the Controller as such
[HttpGet] public IEnumerableGet()
which you would expect to return a collection of TodoItems and all the properties for each. What if you were only interested in Name and Created? The first inclination may be to create another model with only those two properties and return it instead of TodoItem. This would be fine until someone wanted only Name or wanted Completed added. Creating multiple models would quickly become unmaintainable.
OData allows you to shape the data by using a querystring parameters, such as,
- http://localhost/api/todo?$select=Name
- http://localhost/api/todo?$select=Name,Created
In the case of the first query only the Name parameter would be returned in the resultset. Likewise with second, only Name and Created would be returned. This allows for much better support for callers based on their needs and easier to implement, (Actually, there is nothing to implement), then multiple models and endpoints.
Playing well together
For this demo I'll start with a default ASP.NET Core Web API project targeted to .NET 5.0.
To get Swagger, or technically Swashbuckle, to recognize OData and play nice together and generate the appropriate documentation we'll need to add additional Nuget packages and create a couple of helper methods.
To start with, add these Nuget packages to the project.
- Microsoft.OData.Core
- Microsoft.AspNetCore.OData
- Microsoft.AspNetCore.MVC.ApiExplorer
- Microsoft.AspNetCore.MVC.Versioning.ApiExplorer
- Microsoft.AspNetCore.OData.Versioning
- Microsoft.AspNetCore.OData.Versioning.ApiExplorer
As the name implies on the last packages, these add support for versioning. Though technically not required for this scenario, versioning your API is a good practice.
Update Startup.cs and replace ConfigureServices with this code
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddApiVersioning(options => { options.ReportApiVersions = true; options.AssumeDefaultVersionWhenUnspecified = true; options.DefaultApiVersion = new ApiVersion(1, 0); // Allow version to be specified in header options.ApiVersionReader = ApiVersionReader.Combine( new QueryStringApiVersionReader(), new HeaderApiVersionReader("api-version", "x-ms-version")); }); services.AddOData().EnableApiVersioning(); services.AddODataApiExplorer( options => { // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service // note: the specified format code will format the version as "'v'major[.minor][-status]" options.GroupNameFormat = "'v'VVV"; // note: this option is only necessary when versioning by url segment. the SubstitutionFormat // can also be used to control the format of the API version in route templates options.SubstituteApiVersionInUrl = true; }); services.AddTransient<IConfigureOptions, ConfigureSwaggerOptions>(); services.AddSwaggerGen( options => { // add a custom operation filter which sets default values options.OperationFilter<SwaggerDefaultValues>(); // integrate xml comments options.IncludeXmlComments(XmlCommentsFilePath()); }); string XmlCommentsFilePath() { var basePath = PlatformServices.Default.Application.ApplicationBasePath; var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml"; return Path.Combine(basePath, fileName); } }
and replace Configure with this code
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, VersionedODataModelBuilder modelBuilder, IApiVersionDescriptionProvider provider) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { // Indicate what OData methods to support endpoints.Select().Filter().Count().OrderBy(); endpoints.MapVersionedODataRoute("odata", "api", modelBuilder); }); app.UseSwagger(); app.UseSwaggerUI( options => { // build a swagger endpoint for each discovered API version foreach (var description in provider.ApiVersionDescriptions) { options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); } }); }
Again, not going to delve into the specifics of these lines since this article is focused on just getting OData and Swagger working together, not about the nuances of API versioning and Swagger generation.
Now we'll add the classes SwaggerDefaultValues and ConfigureSwaggerOptions
////// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter. /// ///This public class SwaggerDefaultValues : IOperationFilter { ///is only required due to bugs in the . /// Once they are fixed and published, this class can be removed. /// Applies the filter to the specified operation using the given context. /// /// The operation to apply the filter to. /// The current operation filter context. public void Apply(OpenApiOperation operation, OperationFilterContext context) { var apiDescription = context.ApiDescription; operation.Deprecated |= apiDescription.IsDeprecated(); if (operation.Parameters == null) { return; } foreach (var parameter in operation.Parameters) { var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); if (parameter.Description == null) { parameter.Description = description.ModelMetadata?.Description; } if (parameter.Schema.Default == null && description.DefaultValue != null) { parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString()); } parameter.Required |= description.IsRequired; } } }
////// Configures the Swagger generation options. /// ///This allows API versioning to define a Swagger document per API version after the /// public class ConfigureSwaggerOptions : IConfigureOptionsservice has been resolved from the service container. { private readonly IApiVersionDescriptionProvider _provider; private static IConfiguration _configuration; /// /// Initializes a new instance of the /// Theclass. /// provider used to generate Swagger documents. public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider, IConfiguration configuration) { _provider = provider ?? throw new ArgumentNullException(nameof(provider)); _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); } ///public void Configure(SwaggerGenOptions options) { // add a swagger document for each discovered API version // note: you might choose to skip or document deprecated API versions differently foreach (var description in _provider.ApiVersionDescriptions) { options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); } } static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) { var info = new OpenApiInfo(); // Define values in appConfig for extensibility _configuration.GetSection(nameof(OpenApiInfo)).Bind(info); info.License = new OpenApiLicense() { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") }; if (description.IsDeprecated) { info.Description += " This API version has been deprecated."; } return info; } }
We'll also need to generate documentation as the Swagger is built from it.

Now, we'll switch to the WeatherForecastController and add the necessary attributes to it and any methods necessary.
[ApiVersion("1.0")] [ODataRoutePrefix("WeatherForecast")] public class WeatherForecastController : ODataController
Two critical pieces here are that ODataRoutePrefix must match the name of the controller, WeatherForecast, in this case. Also, it must derive from ODataController.
Any method that supports OData will need these attributes applied.
[ODataRoute] [EnableQuery]
The first is, of course, setting the route for OData and the latter is what enables a caller to use OData queries. This attribute has a number of options that allow you to control at the method level what queries may be supported, overrriding the global settings added earlier.
With all of this in place you should now be able to see the Swagger page with the availble methods and OData support.

Conclusion
This article was not written with the intention to go into the depths of OData or Swagger. However, I have hopefully given an example of how to enable them in an ASP.NET Core API project and correct any gotchas that may come up.