|
| 1 | +--- |
| 2 | +title: Upgrade guide to OpenAPI.NET 2.1 |
| 3 | +description: Learn how to upgrade your OpenAPI.NET version from 1.6 to 2.0 |
| 4 | +author: rachit.malik |
| 5 | +ms.author: malikrachit |
| 6 | +ms.topic: conceptual |
| 7 | +--- |
| 8 | +# Introduction |
| 9 | + |
| 10 | +We are excited to announce the preview of a new version of the OpenAPI.NET library! |
| 11 | +OpenAPI.NET v2 is a major update to the OpenAPI.NET library. This release includes a number of performance improvements, API enhancements, and support for OpenAPI v3.1. |
| 12 | + |
| 13 | +## The biggest update ever |
| 14 | + |
| 15 | +Since the release of the first version of the OpenAPI.NET library in 2018, there has not been a major version update to the library. With the addition of support for OpenAPI v3.1 it was necessary to make some breaking changes. With this opportunity, we have taken the time to make some other improvements to the library, based on the experience we have gained supporting a large community of users for the last six years . |
| 16 | + |
| 17 | +## Performance Improvements |
| 18 | + |
| 19 | +One of the key features of OpenAPI.NET is its performance. This version makes it possible to parse JSON based OpenAPI descriptions even faster. OpenAPI.NET v1 relied on the excellent YamlSharp library for parsing both JSON and YAML files. With OpenAPI.NET v2 we are relying on System.Text.Json for parsing JSON files. For YAML files, we continue to use YamlSharp to parse YAML but then convert to JsonNodes for processing. This allows us to take advantage of the performance improvements in System.Text.Json while still supporting YAML files. |
| 20 | + |
| 21 | +In v1, instances of `$ref` were resolved in a second pass of the document to ensure the target of the reference has been parsed before attempting to resolve it. In v2, reference targets are lazily resolved when reference objects are accessed. This improves load time performance for documents that make heavy use of references. |
| 22 | + |
| 23 | +[How does this change the behaviour of external references?] |
| 24 | + |
| 25 | +## Reduced Dependencies |
| 26 | + |
| 27 | +In OpenAPI v1, it was necessary to include the Microsoft.OpenApi.Readers library to be able to read OpenAPI descriptions in either YAML or JSON. In OpenAPI.NET v2, the core Microsoft.OpenAPI library can both read and write JSON. It is only necessary to use the readers library if you need YAML support. This allows teams who are only working in JSON to avoid the additional dependency and therefore eliminate all non-.NET library references. |
| 28 | + |
| 29 | +## API Enhancements |
| 30 | + |
| 31 | +The v1 library attempted to mimic the pattern of `XmlTextReader` and `JsonTextReader` for the purpose of loading OpenAPI documents from strings, streams and textReaders. |
| 32 | + |
| 33 | +```csharp |
| 34 | + var reader = new OpenApiStringReader(); |
| 35 | + var openApiDoc = reader.Read(stringOpenApiDoc, out var diagnostic); |
| 36 | +``` |
| 37 | +The same pattern can be used for `OpenApiStreamReader` and `OpenApiTextReader`. When we introduced the `ReadAsync` methods we eliminated the use of the `out` parameter. |
| 38 | + |
| 39 | +```csharp |
| 40 | + var reader = new OpenApiStreamReader(); |
| 41 | + var readResult = await reader.ReadAsync(streamOpenApiDoc); |
| 42 | +``` |
| 43 | +A `ReadResult` object acts as a tuple of `OpenApiDiagnostic` and `OpenApiDocument`. |
| 44 | + |
| 45 | +The challenge with this approach is that the reader classes are not very discoverable and the behaviour is not actually consistent with `*TextReader` pattern that allows incrementally reading the document. This library does not support incrementally reading the OpenAPI Document. It only reads a complete document and returns an `OpenApiDocument` instance. |
| 46 | + |
| 47 | +In the v2 library we are moving to the pattern used by classes like `XDocument` where a set of static `Load` and `Parse` methods are used as factory methods. |
| 48 | + |
| 49 | +```csharp |
| 50 | +public class OpenApiDocument { |
| 51 | + public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings settings = null) {} |
| 52 | + public static async Task<ReadResult> LoadAsync(Stream stream, string? format = null, OpenApiReaderSettings? settings = null) {} |
| 53 | + public static ReadResult Load(MemoryStream stream, string? format = null, OpenApiReaderSettings? settings = null) {} |
| 54 | + public static ReadResult Parse(string input,string? format = null,OpenApiReaderSettings? settings = null) {} |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +This API design allows a developer to use IDE autocomplete to present all the loading options by simply knowning the name of the `OpenApiDocument` class. Each of these methods are layered ontop of the more primitive methods to ensure consistent behaviour. |
| 59 | + |
| 60 | +As the YAML format is only supported when including the `Microsoft.OpenApi.Readers` library it was decided not to use an enum for the `format` parameter. We are considering implementing a more [strongly type solution](https://github.com/microsoft/OpenAPI.NET/issues/1952) similar to the way that `HttpMethod` is implemented so that we have a strongly typed experience that is also extensible. |
| 61 | + |
| 62 | +Where the loading methods are used without a format property, we will attempt to parse the document using the default JSON reader. If that fails and the Yaml reader is registered, then we will attempt to read as YAML. The goal is always to provide the fastest path with JSON but still maintain the convenience of not having to care whether a URL points to YAML or JSON if you need that flexibility. |
| 63 | + |
| 64 | +### Removing the OpenAPI Any classes |
| 65 | + |
| 66 | +In the OpenAPI specification, there are a few properties that are defined as type `any`. This includes: |
| 67 | +- the example property in the parameter, media type objects |
| 68 | +- the value property in the example object |
| 69 | +- the values in the link object's parameters dictionary and requestBody property |
| 70 | +- all `x-` extension properties |
| 71 | + |
| 72 | +In the v1 library, there are set of classes that are derived from the OpenApiAny base class which an abstract model which reflects the JSON data model plus some additional primitive types such as decimal, float, datetime etc. |
| 73 | + |
| 74 | +In v2 we are removing this abstraction and relying on the JsonNode model to represent these inner types. In v1 we were not able to reliably identify the additional primitive types and it caused a significant amount of false negatives in error reporting as well as incorrectly parsed data values. |
| 75 | + |
| 76 | +Due to JsonNode implicit operators, this makes initialization sometimes easier, instead of: |
| 77 | +```csharp |
| 78 | + new OpenApiParameter |
| 79 | + { |
| 80 | + In = null, |
| 81 | + Name = "username", |
| 82 | + Description = "username to fetch", |
| 83 | + Example = new OpenApiFloat(5), |
| 84 | + }; |
| 85 | +``` |
| 86 | +the assignment becomes simply, |
| 87 | +```csharp |
| 88 | + Example = (float)0.5, |
| 89 | +``` |
| 90 | + |
| 91 | +For a more complex example, where the developer wants to create an extension that is an object they would do this in v1: |
| 92 | + |
| 93 | +```csharp |
| 94 | + var openApiObject = new OpenApiObject |
| 95 | + { |
| 96 | + {"stringProp", new OpenApiString("stringValue1")}, |
| 97 | + {"objProp", new OpenApiObject()}, |
| 98 | + { |
| 99 | + "arrayProp", |
| 100 | + new OpenApiArray |
| 101 | + { |
| 102 | + new OpenApiBoolean(false) |
| 103 | + } |
| 104 | + } |
| 105 | + }; |
| 106 | + var parameter = new OpenApiParameter(); |
| 107 | + parameter.Extensions.Add("x-foo", new OpenApiAny(openApiObject)); |
| 108 | + |
| 109 | +``` |
| 110 | + |
| 111 | +In v2, the equivalent code would be, |
| 112 | + |
| 113 | +```csharp |
| 114 | + var openApiObject = new JsonObject |
| 115 | + { |
| 116 | + {"stringProp", "stringValue1"}, |
| 117 | + {"objProp", new JsonObject()}, |
| 118 | + { |
| 119 | + "arrayProp", |
| 120 | + new JsonArray |
| 121 | + { |
| 122 | + false |
| 123 | + } |
| 124 | + } |
| 125 | + }; |
| 126 | + var parameter = new OpenApiParameter(); |
| 127 | + parameter.Extensions.Add("x-foo", new OpenApiAny(openApiObject)); |
| 128 | + |
| 129 | +``` |
| 130 | + |
| 131 | + |
| 132 | +### Updates to OpenApiSchema |
| 133 | + |
| 134 | +The OpenAPI 3.1 specification changes significantly how it leverages JSON Schema. In 3.0 and earlier, OpenAPI used a "subset, superset" of JSON Schema draft-4. This caused many problems for developers trying to use JSON Schema validation libraries with the JSON Schema in their OpenAPI descriptions. In OpenAPI 3.1, the 2020-12 draft version of JSON Schema was adopted and a new JSON Schema vocabulary was adopted to support OpenAPI specific keywords. All attempts to constrain what JSON Schema keywords could be used in OpenAPI were removed. |
| 135 | + |
| 136 | + |
| 137 | +#### New keywords introduced in 2020-12 |
| 138 | + |
| 139 | +```csharp |
| 140 | + /// $schema, a JSON Schema dialect identifier. Value must be a URI |
| 141 | + public virtual string Schema { get; set; } |
| 142 | + /// $id - Identifies a schema resource with its canonical URI. |
| 143 | + public virtual string Id { get; set; } |
| 144 | + /// $comment - reserves a location for comments from schema authors to readers or maintainers of the schema. |
| 145 | + public virtual string Comment { get; set; } |
| 146 | + /// $vocabulary- used in meta-schemas to identify the vocabularies available for use in schemas described by that meta-schema. |
| 147 | + public virtual IDictionary<string, bool> Vocabulary { get; set; } |
| 148 | + /// $dynamicRef - an applicator that allows for deferring the full resolution until runtime, at which point it is resolved each time it is encountered while evaluating an instance |
| 149 | + public virtual string DynamicRef { get; set; } |
| 150 | + /// $dynamicAnchor - used to create plain name fragments that are not tied to any particular structural location for referencing purposes, which are taken into consideration for dynamic referencing. |
| 151 | + public virtual string DynamicAnchor { get; set; } |
| 152 | + /// $defs - reserves a location for schema authors to inline re-usable JSON Schemas into a more general schema. |
| 153 | + public virtual IDictionary<string, OpenApiSchema> Definitions { get; set; } |
| 154 | + public virtual IDictionary<string, OpenApiSchema> PatternProperties { get; set; } = new Dictionary<string, OpenApiSchema>(); |
| 155 | + public virtual bool UnevaluatedProperties { get; set;} |
| 156 | + public virtual bool UnEvaluatedProperties { get; set; } // Duplicate should be removed |
| 157 | +
|
| 158 | +``` |
| 159 | +#### Changes to existing keywords |
| 160 | + |
| 161 | +```csharp |
| 162 | + |
| 163 | + public virtual decimal? ExclusiveMaximum { get; set; } // New, but currently named v31ExclusiveMaximum |
| 164 | + public virtual decimal? ExclusiveMinimum { get; set; } // New, but Currently named v31ExclusiveMinimum) |
| 165 | + public virtual bool? ExclusiveMaximum { get; set; } // To be removed |
| 166 | + public virtual bool? ExclusiveMinimum { get; set; } // To be removed |
| 167 | +
|
| 168 | + public virtual JsonSchemaType? Type { get; set; } // Was string, now flagged enum |
| 169 | + public virtual decimal? Maximum { get; set; } // Double??? |
| 170 | + public virtual decimal? Minimum { get; set; } // Double??? |
| 171 | +
|
| 172 | + public virtual JsonNode Default { get; set; } // Type matching no longer enforced. Was IOpenApiAny |
| 173 | + public virtual bool ReadOnly { get; set; } // No longer has defined semantics in OpenAPI 3.1 |
| 174 | + public virtual bool WriteOnly { get; set; } // No longer has defined semantics in OpenAPI 3.1 |
| 175 | + public virtual bool UnresolvedReference { get; set; } // Can be removed |
| 176 | + public virtual OpenApiReference Reference { get; set; } // Can be removed |
| 177 | +
|
| 178 | + public virtual JsonNode Example { get; set; } // No longer IOpenApiAny |
| 179 | + public virtual IList<JsonNode> Examples { get; set; } |
| 180 | + public virtual IList<JsonNode> Enum { get; set; } = new List<JsonNode>(); |
| 181 | + |
| 182 | + public virtual bool Nullable { get; set; } // To be removed |
| 183 | + public virtual OpenApiExternalDocs ExternalDocs { get; set; } // OpenApi Vocab |
| 184 | + public virtual bool Deprecated { get; set; } // OpenApi Vocab |
| 185 | + public virtual OpenApiXml Xml { get; set; } // OpenApi Vocab |
| 186 | +
|
| 187 | + public IDictionary<string, object> Annotations { get; set; } // Custom keywords? |
| 188 | +``` |
| 189 | + |
| 190 | +#### Potential Performance improvements |
| 191 | + |
| 192 | +```csharp |
| 193 | + public virtual IDictionary<string, OpenApiSchema> PatternProperties { get; set; } = new Dictionary<string, OpenApiSchema>(); |
| 194 | + public virtual IList<JsonNode> Enum { get; set; } = new List<JsonNode>(); |
| 195 | + public virtual IList<OpenApiSchema> AllOf { get; set; } = new List<OpenApiSchema>(); |
| 196 | + public virtual IList<OpenApiSchema> OneOf { get; set; } = new List<OpenApiSchema>(); |
| 197 | + public virtual IList<OpenApiSchema> AnyOf { get; set; } = new List<OpenApiSchema>(); |
| 198 | + public virtual ISet<string> Required { get; set; } = new HashSet<string>(); |
| 199 | + public virtual IDictionary<string, OpenApiSchema> Properties { get; set; } = new Dictionary<string, OpenApiSchema>(); |
| 200 | + public virtual IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>(); |
| 201 | +``` |
| 202 | + |
| 203 | +#### OpenApiSchema methods |
| 204 | + |
| 205 | +Other than the addition of the SerializeAsV31, the methods have not changed. |
| 206 | + |
| 207 | +```csharp |
| 208 | +public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiReferenceable, IOpenApiSerializable |
| 209 | +{ |
| 210 | + public OpenApiSchema() { } |
| 211 | + public OpenApiSchema(OpenApiSchema schema) { } |
| 212 | + public virtual void SerializeAsV31(IOpenApiWriter writer) { } |
| 213 | + public virtual void SerializeAsV3(IOpenApiWriter writer) { } |
| 214 | + public virtual void SerializeAsV2(IOpenApiWriter writer) { } |
| 215 | +} |
| 216 | + |
| 217 | +``` |
| 218 | + |
| 219 | +## OpenAPI v3.1 Support |
| 220 | + |
| 221 | +There are a number of new features in OpenAPI v3.1 that are now supported in OpenAPI.Net. |
| 222 | + |
| 223 | +### Webhooks |
| 224 | + |
| 225 | +```csharp |
| 226 | + |
| 227 | +public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IOpenApiAnnotatable { |
| 228 | + /// <summary> |
| 229 | + /// The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement. |
| 230 | + /// A map of requests initiated other than by an API call, for example by an out of band registration. |
| 231 | + /// The key name is a unique string to refer to each webhook, while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider and the expected responses |
| 232 | + /// </summary> |
| 233 | + public IDictionary<string, OpenApiPathItem>? Webhooks { get; set; } = new Dictionary<string, OpenApiPathItem>(); |
| 234 | + |
| 235 | +} |
| 236 | +``` |
| 237 | + |
| 238 | +### Summary in info object |
| 239 | + |
| 240 | +```csharp |
| 241 | + |
| 242 | + /// <summary> |
| 243 | + /// Open API Info Object, it provides the metadata about the Open API. |
| 244 | + /// </summary> |
| 245 | + public class OpenApiInfo : IOpenApiSerializable, IOpenApiExtensible |
| 246 | + { |
| 247 | + /// <summary> |
| 248 | + /// A short summary of the API. |
| 249 | + /// </summary> |
| 250 | + public string Summary { get; set; } |
| 251 | + } |
| 252 | +``` |
| 253 | + |
| 254 | + |
| 255 | +### License SPDX identifiers |
| 256 | + |
| 257 | +```csharp |
| 258 | +/// <summary> |
| 259 | + /// License Object. |
| 260 | + /// </summary> |
| 261 | + public class OpenApiLicense : IOpenApiSerializable, IOpenApiExtensible |
| 262 | + { |
| 263 | + /// <summary> |
| 264 | + /// An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. |
| 265 | + /// </summary> |
| 266 | + public string Identifier { get; set; } |
| 267 | + } |
| 268 | +``` |
| 269 | + |
| 270 | +### Reusable path items |
| 271 | + |
| 272 | +```csharp |
| 273 | + /// <summary> |
| 274 | + /// Components Object. |
| 275 | + /// </summary> |
| 276 | + public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible |
| 277 | + { |
| 278 | + /// <summary> |
| 279 | + /// An object to hold reusable <see cref="OpenApiPathItem"/> Object. |
| 280 | + /// </summary> |
| 281 | + public virtual IDictionary<string, OpenApiPathItem>? PathItems { get; set; } = new Dictionary<string, OpenApiPathItem>(); |
| 282 | + |
| 283 | + } |
| 284 | +``` |
| 285 | + |
| 286 | +#### Summary and Description alongside $ref |
| 287 | + |
| 288 | +Through the use of proxy objects in order to represent references, it is now possible to set the Summary and Description property on an object that is a reference. This was previously not possible. |
| 289 | + |
| 290 | +```csharp |
| 291 | + var parameter = new OpenApiParameterReference("id", hostdocument) |
| 292 | + { |
| 293 | + Description = "Customer Id" |
| 294 | + }; |
| 295 | +``` |
| 296 | +### Use HTTP Method Object Instead of Enum |
| 297 | +HTTP methods are now represented as objects instead of enums. This change enhances flexibility but requires updates to how HTTP methods are handled in your code. |
| 298 | +Example: |
| 299 | +```csharp |
| 300 | +// Before (1.6) |
| 301 | +OpenApiOperation operation = new OpenApiOperation |
| 302 | +{ |
| 303 | + HttpMethod = OperationType.Get |
| 304 | +}; |
| 305 | + |
| 306 | +// After (2.0) |
| 307 | +OpenApiOperation operation = new OpenApiOperation |
| 308 | +{ |
| 309 | + HttpMethod = new HttpMethod("GET") |
| 310 | +}; |
| 311 | +``` |
| 312 | + |
| 313 | +#### 2. Enable Null Reference Type Support |
| 314 | +Version 2.0 preview 13 introduces support for null reference types, which improves type safety and reduces the likelihood of null reference exceptions. |
| 315 | + |
| 316 | +**Example:** |
| 317 | +```csharp |
| 318 | +// Before (1.6) |
| 319 | +OpenApiDocument document = new OpenApiDocument |
| 320 | +{ |
| 321 | + Components = new OpenApiComponents() |
| 322 | +}; |
| 323 | + |
| 324 | +// After (2.0) |
| 325 | +OpenApiDocument document = new OpenApiDocument |
| 326 | +{ |
| 327 | + Components = new OpenApiComponents() |
| 328 | + { |
| 329 | + Schemas = new Dictionary<string, OpenApiSchema?>() |
| 330 | + } |
| 331 | +}; |
| 332 | + |
| 333 | +``` |
| 334 | + |
| 335 | +#### 3. References as Components |
| 336 | +References can now be used as components, allowing for more modular and reusable OpenAPI documents. |
| 337 | + |
| 338 | +**Example:** |
| 339 | +```csharp |
| 340 | +// Before (1.6) |
| 341 | +OpenApiSchema schema = new OpenApiSchema |
| 342 | +{ |
| 343 | + Reference = new OpenApiReference |
| 344 | + { |
| 345 | + Type = ReferenceType.Schema, |
| 346 | + Id = "MySchema" |
| 347 | + } |
| 348 | +}; |
| 349 | + |
| 350 | +// After (2.0) |
| 351 | +OpenApiComponents components = new OpenApiComponents |
| 352 | +{ |
| 353 | + Schemas = new Dictionary<string, OpenApiSchema> |
| 354 | + { |
| 355 | + ["MySchema"] = new OpenApiSchema |
| 356 | + { |
| 357 | + Reference = new OpenApiReference |
| 358 | + { |
| 359 | + Type = ReferenceType.Schema, |
| 360 | + Id = "MySchema" |
| 361 | + } |
| 362 | + } |
| 363 | + } |
| 364 | +}; |
| 365 | +``` |
| 366 | + |
| 367 | +### OpenApiDocument.SerializeAs() |
| 368 | +The `SerializeAs()` method simplifies serialization scenarios, making it easier to convert OpenAPI documents to different formats. |
| 369 | +**Example:** |
| 370 | +```csharp |
| 371 | +OpenApiDocument document = new OpenApiDocument();string json = document.SerializeAs(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Json); |
| 372 | + |
| 373 | +### Bug Fixes#### Serialization of ReferencesFixed a bug where references would not serialize summary or descriptions in OpenAPI 3.1.**Example:** |
| 374 | +OpenApiSchema schema = new OpenApiSchema |
| 375 | +{ |
| 376 | + Reference = new OpenApiReference |
| 377 | + { |
| 378 | + Type = ReferenceType.Schema, |
| 379 | + Id = "MySchema" |
| 380 | + }, |
| 381 | + Summary = "This is a summary", |
| 382 | + Description = "This is a description" |
| 383 | +}; |
| 384 | +``` |
| 385 | + |
| 386 | + |
| 387 | +## Feedback |
| 388 | + If you have any feedback please file a GitHub issue here: https://github.com/microsoft/OpenAPI.NET/issues |
| 389 | + The team is looking forward to hear your experience trying the new version and we hope you have fun busting out your OpenAPI 3.1 descriptions. |
0 commit comments