NSwag provides a comprehensive set of attributes (annotations) for customizing OpenAPI document generation. These attributes allow developers to add metadata, customize operation behavior, and control how APIs are documented without modifying the underlying business logic.
Specifies operation metadata including ID, summary, and description.
[AttributeUsage(AttributeTargets.Method)]
public class OpenApiOperationAttribute : Attribute
{
public OpenApiOperationAttribute(string operationId);
public OpenApiOperationAttribute(string summary, string description);
public OpenApiOperationAttribute(string operationId, string summary, string description);
public string Summary { get; }
public string Description { get; }
}[HttpGet("{id}")]
[OpenApiOperation(
"GetUserById",
"Retrieve a user by ID",
"Gets a specific user from the database using their unique identifier"
)]
public async Task<ActionResult<User>> GetUser(int id)
{
// Implementation
}Adds a single tag to an operation or controller.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class OpenApiTagAttribute : Attribute
{
public OpenApiTagAttribute(string name);
public string Name { get; set; }
public string Description { get; set; }
public string DocumentationDescription { get; set; }
public string DocumentationUrl { get; set; }
public bool AddToDocument { get; set; }
}[OpenApiTag("Users")]
public class UsersController : ControllerBase
{
// Controller actions
}
// Or with additional properties:
// [OpenApiTag("Users") { Description = "Operations related to user management", DocumentationUrl = "https://docs.example.com/users" }]Adds multiple tags to an operation.
[AttributeUsage(AttributeTargets.Method)]
public class OpenApiTagsAttribute : Attribute
{
public string[] Tags { get; set; }
public OpenApiTagsAttribute(params string[] tags);
}[HttpPost]
[OpenApiTags("Users", "Creation", "Authentication")]
public async Task<ActionResult<User>> CreateUser(CreateUserRequest request)
{
// Implementation
}Specifies metadata for request body parameters.
[AttributeUsage(AttributeTargets.Parameter)]
public class OpenApiBodyParameterAttribute : Attribute
{
public string Name { get; set; }
public string Description { get; set; }
public Type Type { get; set; }
public bool Required { get; set; } = true;
}[HttpPost]
public async Task<ActionResult> UpdateUser(
[OpenApiBodyParameter(
Name = "userData",
Description = "User data to update",
Type = typeof(UpdateUserRequest),
Required = true
)] UpdateUserRequest request)
{
// Implementation
}Specifies file upload parameters.
[AttributeUsage(AttributeTargets.Parameter)]
public class OpenApiFileAttribute : Attribute
{
public string Name { get; set; }
public string Description { get; set; }
public bool Required { get; set; } = true;
}[HttpPost("upload")]
public async Task<ActionResult> UploadFile(
[OpenApiFile(
Name = "file",
Description = "The file to upload",
Required = true
)] IFormFile file)
{
// Implementation
}Specifies the response type and status code for an operation.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class ResponseTypeAttribute : Attribute
{
public int StatusCode { get; set; }
public Type Type { get; set; }
public string Description { get; set; }
public ResponseTypeAttribute(int statusCode);
public ResponseTypeAttribute(int statusCode, Type type);
}[HttpGet("{id}")]
[ResponseType(200, typeof(User), Description = "User found and returned")]
[ResponseType(404, Description = "User not found")]
[ResponseType(400, typeof(ValidationError), Description = "Invalid user ID")]
public async Task<ActionResult<User>> GetUser(int id)
{
// Implementation
}Legacy response specification attribute (maintained for backward compatibility).
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class SwaggerResponseAttribute : Attribute
{
public int StatusCode { get; set; }
public Type Type { get; set; }
public string Description { get; set; }
public SwaggerResponseAttribute(int statusCode, Type type = null, string description = null);
}Specifies the default response for an operation.
[AttributeUsage(AttributeTargets.Method)]
public class SwaggerDefaultResponseAttribute : Attribute
{
public Type Type { get; set; }
public string Description { get; set; }
public SwaggerDefaultResponseAttribute(Type type = null, string description = null);
}Excludes operations, parameters, or properties from OpenAPI generation.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property)]
public class OpenApiIgnoreAttribute : Attribute
{
}// Ignore entire operation
[HttpGet("internal")]
[OpenApiIgnore]
public ActionResult InternalOperation()
{
// This operation won't appear in OpenAPI spec
}
// Ignore parameter
[HttpGet]
public ActionResult GetData(
string publicParam,
[OpenApiIgnore] string internalParam)
{
// internalParam won't appear in OpenAPI spec
}
// Ignore property in model
public class User
{
public string Name { get; set; }
[OpenApiIgnore]
public string InternalId { get; set; }
}Specifies controller-level metadata.
[AttributeUsage(AttributeTargets.Class)]
public class OpenApiControllerAttribute : Attribute
{
public string Name { get; set; }
public string Description { get; set; }
}[OpenApiController(
Name = "User Management",
Description = "API endpoints for managing user accounts"
)]
public class UsersController : ControllerBase
{
// Controller actions
}Adds custom extension data to operations.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class OpenApiExtensionDataAttribute : Attribute
{
public string Key { get; set; }
public object Value { get; set; }
public OpenApiExtensionDataAttribute(string key, object value);
}[HttpPost]
[OpenApiExtensionData("x-rate-limit", 100)]
[OpenApiExtensionData("x-cache-duration", "5m")]
public async Task<ActionResult> CreateResource(CreateResourceRequest request)
{
// Implementation
}Specifies a custom operation processor for an operation.
[AttributeUsage(AttributeTargets.Method)]
public class OpenApiOperationProcessorAttribute : Attribute
{
public Type Type { get; set; }
public object[] Arguments { get; set; }
public OpenApiOperationProcessorAttribute(Type type, params object[] arguments);
}[HttpGet]
[OpenApiOperationProcessor(typeof(CustomOperationProcessor), "customArgument")]
public ActionResult GetData()
{
// Custom processor will be applied to this operation
}
// Custom processor implementation
public class CustomOperationProcessor : IOperationProcessor
{
private readonly string _argument;
public CustomOperationProcessor(string argument)
{
_argument = argument;
}
public bool Process(OperationProcessorContext context)
{
// Custom processing logic
return true;
}
}Indicates that an operation will read the request body, affecting parameter binding.
[AttributeUsage(AttributeTargets.Method)]
public class WillReadBodyAttribute : Attribute
{
}[HttpPost]
[WillReadBody]
public async Task<ActionResult> ProcessData([FromBody] string rawData)
{
// This operation reads the raw request body
}Here's a comprehensive example showing multiple annotations working together:
[ApiController]
[Route("api/[controller]")]
[OpenApiController(
Name = "User Management API",
Description = "Complete user lifecycle management"
)]
[OpenApiTag(
Name = "Users",
Description = "User account operations",
ExternalDocumentationUrl = "https://docs.example.com/users"
)]
public class UsersController : ControllerBase
{
[HttpGet]
[OpenApiOperation(
OperationId = "GetAllUsers",
Summary = "List all users",
Description = "Retrieves a paginated list of all users in the system"
)]
[ResponseType(200, typeof(PagedResult<User>), Description = "Users retrieved successfully")]
[ResponseType(400, typeof(ValidationError), Description = "Invalid pagination parameters")]
[OpenApiExtensionData("x-rate-limit", 1000)]
public async Task<ActionResult<PagedResult<User>>> GetUsers(
[FromQuery] int page = 1,
[FromQuery] int size = 20,
[OpenApiIgnore] string internalFilter = null)
{
// Implementation
}
[HttpGet("{id}")]
[OpenApiOperation(
OperationId = "GetUserById",
Summary = "Get user by ID",
Description = "Retrieves a specific user by their unique identifier"
)]
[ResponseType(200, typeof(User), Description = "User found")]
[ResponseType(404, Description = "User not found")]
[ResponseType(400, typeof(ValidationError), Description = "Invalid user ID")]
public async Task<ActionResult<User>> GetUser(int id)
{
// Implementation
}
[HttpPost]
[OpenApiOperation(
OperationId = "CreateUser",
Summary = "Create new user",
Description = "Creates a new user account with the provided information"
)]
[OpenApiTags("Users", "Creation")]
[ResponseType(201, typeof(User), Description = "User created successfully")]
[ResponseType(400, typeof(ValidationError), Description = "Invalid user data")]
[ResponseType(409, Description = "User already exists")]
public async Task<ActionResult<User>> CreateUser(
[OpenApiBodyParameter(
Name = "user",
Description = "User information for account creation",
Required = true
)] CreateUserRequest request)
{
// Implementation
}
[HttpPost("{id}/avatar")]
[OpenApiOperation(
OperationId = "UploadUserAvatar",
Summary = "Upload user avatar",
Description = "Uploads an avatar image for the specified user"
)]
[ResponseType(200, typeof(User), Description = "Avatar uploaded successfully")]
[ResponseType(400, typeof(ValidationError), Description = "Invalid file or user ID")]
[ResponseType(404, Description = "User not found")]
public async Task<ActionResult<User>> UploadAvatar(
int id,
[OpenApiFile(
Name = "avatar",
Description = "Avatar image file (JPEG, PNG, max 5MB)",
Required = true
)] IFormFile avatar)
{
// Implementation
}
[HttpDelete("{id}")]
[OpenApiOperation(
OperationId = "DeleteUser",
Summary = "Delete user",
Description = "Permanently deletes a user account and all associated data"
)]
[ResponseType(204, Description = "User deleted successfully")]
[ResponseType(404, Description = "User not found")]
[ResponseType(409, Description = "User cannot be deleted due to dependencies")]
[OpenApiExtensionData("x-destructive", true)]
public async Task<ActionResult> DeleteUser(int id)
{
// Implementation
}
[HttpGet("internal-stats")]
[OpenApiIgnore] // This endpoint won't appear in OpenAPI documentation
public ActionResult GetInternalStats()
{
// Internal operation not exposed in API documentation
}
}
// Supporting models with annotations
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
[OpenApiIgnore] // Internal tracking field
public DateTime InternalTimestamp { get; set; }
}
public class CreateUserRequest
{
[Required]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
public string Bio { get; set; }
}This comprehensive example demonstrates how annotations can be used to create rich, well-documented APIs with proper metadata, response specifications, and custom behaviors while maintaining clean separation between business logic and API documentation concerns.