Add default value to Swagger path parameters - c#

I have made a WebAPI in .NET CORE 6.
I have a controller class like this:
[ApiController]
[Route("{culture:culture}/[controller]")]
[SwaggerDefaultValue("culture", "en-US")]
[Produces("application/json")]
public class AccountsController : BaseController
{
...
}
As you can see I defined a parameter in the route like this {culture:culture}.
I would like to have a default value for this parameter in my Swagger.
I defined an attribute class like this:
[AttributeUsage(AttributeTargets.Class )]
public class SwaggerDefaultValueAttribute:Attribute
{
public string Name { get; set; }
public string Value { get; set; }
public SwaggerDefaultValueAttribute(string name, string value)
{
Name = name;
Value = value;
}
}
And a filter class like this:
public class SwaggerDefaultValueFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// needs some code
}
}
And added the filter to my Swagger service too.
options.OperationFilter<SwaggerDefaultValueFilter>();
However, the problem is most of the code samples that I found are related to the old versions of Swagger and most of their methods are deprecated (like this one).
The question is, how can I modify this SwaggerDefaultValueFilter class to show a default value in my path parameter:
FYI: I am using <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3" />.
I found this sample too, however, it does not set the default values of the path parameters, it seems it works for model attributes.

To assign the default value for the parameter, you can do something like this,
internal static class OperationFilterContextExtensions
{
public static IEnumerable<T> GetControllerAndActionAttributes<T>(this OperationFilterContext context) where T : Attribute
{
var controllerAttributes = context.MethodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes<T>();
var actionAttributes = context.MethodInfo.GetCustomAttributes<T>();
var result = new List<T>(controllerAttributes);
result.AddRange(actionAttributes);
return result;
}
}
public class SwaggerDefaultValueFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var defaultAttributes = context.GetControllerAndActionAttributes<SwaggerDefaultValueAttribute>();
if (defaultAttributes.Any())
{
foreach (var defaultAttribute in defaultAttributes)
{
var parameter = operation.Parameters.FirstOrDefault(d => d.Name == defaultAttribute.Name);
if (parameter != null)
{
version.Schema.Default = new OpenApiString(defaultAttribute.Value);
}
}
}
}
}

Not Swashbuckle.AspNetCore but this might send you on the right path:
Controller:
https://github.com/heldersepu/Swagger-Net-Test/blob/master/Swagger_Test/Controllers/CompanyController.cs#L35-L39
[Route("Get2")]
public Company Get2([FromUri] Company c)
{
return c;
}
Model:
https://github.com/heldersepu/Swagger-Net-Test/blob/master/Swagger_Test/Models/Company.cs
public class Company
{
/// <summary>The Unique Company ID</summary>
/// <example>123</example>
[Required]
[DefaultValue(456)]
public int Id { get; set; }
[Required]
[DefaultValue(null)]
public int? MyId { get; set; }
/// <summary>The Company Name</summary>
/// <example>Acme co</example>
[DefaultValue("My Company")]
public string Name { get; set; }
Live code:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=Company#/Company/Company_Get2

Related

FromBody value not overwriting default parameter value after migrating to .net core

I have the following endpoint implemented in .Net Core 3.1 WebApi. The TestFilter object has a list parameter TestTypes. TestTypes gets a default TestType value once it is constructed. When the endpoint gets called with the TestFilter parameter I would expect that the default value would be replaced with the incoming value. Instead it's added to the list so the default value is still part of it.
This worked when using .Net472 but after migrating to .Net Core 3.1 the default value is always part of the array.
Is there a way to specify to overwrite the default parameter value if it's supplied by the client?
[HttpPost]
[Route("test")]
public async Task<IHttpActionResult> GetTests([FromBody] TestFilter filter)
{
// Call repo
}
public class TestFilter
{
public IReadOnlyCollection<TestType> TestTypes { get; set; }
public string Description;
TestFilter() {
TestTypes = new List
{
new TestType("AdvancedTest", 10)
};
}
}
Try to change your TestFilter like this:
public class TestFilter
{
public IReadOnlyCollection<TestType> TestTypes { get; set; } = new List<TestType>{new TestType("AdvancedTest", 10)};
public string Description;
}
TestType:
public class TestType
{
public string v1 { get; set; }
public int v2 { get; set; }
public TestType() { }
public TestType(string v1, int v2)
{
this.v1 = v1;
this.v2 = v2;
}
}
Result:
Update:
I use the following classes and it can work.
public class TestFilter: TestBaseFilter
{
public string Description;
}
public class TestBaseFilter
{
public IReadOnlyCollection<TestType> TestTypes { get; set; } = new List<TestType> { new TestType("AdvancedTest", 10) };
}

How to get the summary of the controller and action

I am implementing IApiDescriptionGroupCollectionProvider to get the API description.
I have implemented in this way
private readonly IApiDescriptionGroupCollectionProvider _apiExplorer;
public RouteController(
IApiDescriptionGroupCollectionProvider apiExplorer)
{
_apiExplorer = apiExplorer;
}
[HttpGet("all")]
public IActionResult GetRoute()
{
var paths = GetApiDescriptionsFor("v1");
return Ok();
}
I want to bind all the details of the controller to an ApiRouteDocument custom model with a description of the action too. But the interface I have implemented doesn't give a summary of the action, and the controller. Is there any built-in interface to extract the summary from the actions?
I wanted to avoid the Reflection.
[ApiController]
[Route("api/contact")]
[ApiExplorerSettings(GroupName = "Contact")]
public class ContactController : ControllerBase
{
/// <summary>
/// Get by Name Contact
/// </summary>
[HttpGet("getbyname/{name}")]
public async Task<IActionResult> GetByName(string name)
{
return Ok();
}
}
public class ApiRouteDocument
{
//controllername tag
public string ControllerName { get; set; }
public string ControllerDescription { get; set; }
public IList<RoutePath> Paths;
}
public class RoutePath
{
//get
public string Method { get; set; }
//operationid
public string Name { get; set; }
//summary
public string Description { get; set; }
//path
public string Path { get; set; }
}
You have to run this code after the middleware pipeline has been established (and app.UseEndpoints() has been executed), so it should run inside a controller, or an endpoint, for example.
Because documentation comments aren't included in the assembly, you need to use attributes to annotate your classes & actions. You have the option to use the ones provided by Microsoft, such as [DisplayName], [Description], etc. under System.ComponentModel.Primitives namespace.
Or you can create your own attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
internal class SummaryAttribute : Attribute
{
public string Summary { get; }
public SummaryAttribute(string summary)
{
Summary = summary;
}
}
Then inject IEnumerable<EndpointDataSource> in a controller, which will give you the list of discovered endpoints.
You can get reflected runtime type info from ControllerActionDescriptor, then extract the attributes you've added to controllers & actions.
[Description("Provides info")]
[Route("info")]
public class ThingsController : ControllerBase
{
private IEnumerable<EndpointDataSource> _endpointDataSources;
public ThingsController(IEnumerable<EndpointDataSource> endpointDataSources)
{
_endpointDataSources = endpointDataSources;
}
[Description("Returns a list of endpoints")]
[HttpGet("endpoints")]
public ActionResult Endpoints()
{
var actions = _endpointDataSources.SelectMany(it => it.Endpoints)
.OfType<RouteEndpoint>()
.Where(
it => it.Metadata
.OfType<ControllerActionDescriptor>()
.Any()
)
.Select(
e => {
var actionDescriptor = e.Metadata
.OfType<ControllerActionDescriptor>()
.First();
var isControllerIgnored = actionDescriptor.ControllerTypeInfo.GetCustomAttribute<ApiExplorerSettingsAttribute>()?.IgnoreApi ?? false;
var isActionIgnored = actionDescriptor.MethodInfo .GetCustomAttribute<ApiExplorerSettingsAttribute>()?.IgnoreApi ?? false;
return new
{
ControllerName = actionDescriptor.ControllerName,
ControllerType = actionDescriptor.ControllerTypeInfo.FullName,
ActionDescription = actionDescriptor.MethodInfo.GetCustomAttribute<DescriptionAttribute>()?.Description,
ControllerDescription = actionDescriptor.ControllerTypeInfo
.GetCustomAttribute<DescriptionAttribute>()
?.Description,
Method = actionDescriptor.EndpointMetadata.OfType<HttpMethodMetadata>()
.FirstOrDefault()
?.HttpMethods?[0],
Path = $"/{e.RoutePattern!.RawText!.TrimStart('/')}",
};
}
)
.ToList();
return Ok(actions);
}
}
when you visit /info/endpoints, you'll get a list of endpoints:
[
{
"controllerName": "Things",
"controllerType": "ApiPlayground.ThingsController",
"actionDescription": "Returns a list of endpoints",
"controllerDescription": "Provides info",
"method": "GET",
"path": "/info/endpoints"
}
]

WebApi returns empty JSON instead of serialized object

So simple yet I know not why it fails. In a WebAPI 2.0 ASP.NET MVC (pre-core) controller method, I have this:
[Route("GetItem")]
[HttpGet]
public ItemVM GetItem() {
var item = new ItemVM(); // Constructor initializes
return item;
}
When I run the code, the debugger shows this in item:
item {ViewModels.ItemVMs.ItemVM}
firstItem {ViewModels.ItemVMs.FirstItemVM}
id 0
archived false
name null
Yet WebAPI returns only this:
{}
I have tried suggestions like Newtonsoft json serializer returns empty object but Visual Studio 2017 says CreateProperties does not exist to override.
Any help would be much appreciated.
EDIT:
LOL I told you it is a simple answer. Here's the class:
public class ItemVM {
FirstItemVM firstItem { get; set; }
public ItemVM() {
this.firstItem = FirstItemVM.ToMap( new Entities.Item());
}
}
public class FirstItemVM {
public int id { get; set; }
public bool archived { get; set; }
public string name { get; set; }
public static readonly Expression<Func<Entities.FirstItem, FirstItemVM>>
Map (e) => new FirstItemVM {
id = e.id,
archived = e.archived,
name = e.name
};
public static readonly Func<Entities.FirstItem, FirstItemVM>
ToMap = FirstItemVM.Map.Compile();
}
Most likely ItemVM.firstItem is not public (for example, internal), and JSON serializer will only serialize public properties by default (unless you non-public property explicitly to be serialized).

C# Mongo serialization issue [duplicate]

I've the following mongodb document schema;
{
"_id" : ObjectId("5c9d34ff781318afb9e8ab43"),
"name" : "Name",
"slug" : "slug",
"services" : {
"subservice" : {
"id" : NumberInt(37030)
}
}
}
and then i define my classes as;
public class MainModel
{
public ObjectId Id { get; set; }
[BsonElement("name")]
public string Name { get; set; }
[BsonElement("slug")]
public string Slug { get; set; }
[BsonElement("services")]
public ServicesDef Services { get; set; }
public class ServicesDef
{
[BsonElement("subservice")]
public SubServiceDef SubService{ get; set; }
public class SubServiceDef
{
[BsonElement("id")]
public int Id { get; set; }
}
}
}
But somehow when I query the document;
var result = await Repository.FindAsync(x => x.Slug == slug);
That services.subservice.id isn't properly registered and getting
Element 'id' does not match any field or property of class SubServiceDef.
Stuck here and looking for advice.
I think I'm having the same issue with cannot deserialize with the "Id" attribute but seems there is solution yet.
Long story short: it's all about conventions. MongoDB .NET driver exposes static class ConventionRegistry which allows you to register your own conventions (more here). Additionally there are two "built-in" conventions __defaults__ and __attributes__. Digging deeper (driver github) you can find that it registers one quite interesting convention:
new NamedIdMemberConvention(new [] { "Id", "id", "_id" })
Which means that id members will be considered as regular BSON _id elements.
How to fix that ?
You can get rid of default conventions
ConventionRegistry.Remove("__defaults__");
However automatically you will drop all the other driver conventions which is pretty risky. Alternatively you can create a fake property which will always be empty:
public class SubServiceDef
{
[BsonElement("id")]
public int Id { get; set; }
[BsonId]
public ObjectId FakeId { get; set; }
}
or you can just use BsonNoId attribute which
Specifies that the class's IdMember should be null.
[BsonNoId]
public class SubServiceDef
{
[BsonElement("id")]
public int Id { get; set; }
}
So the convention will be setting your id as IdMember in class map but then during postprocessing this attribute will force IdMember to be null and your class will get deserialized succesfully
I like the answer from #mickl. The issue i had is couldn't update model and add attributes. Also I needed the original Ids and not nulls after deserialization.
I tried BsonClassMap but i had so many sub models to update.
so, i ended up using your idea with removing default conventions.
public class MongoDbDefaultConventionPack : IConventionPack
{
// private static fields
private static readonly IConventionPack __defaultConventionPack = new MongoDbDefaultConventionPack();
// private fields
private readonly IEnumerable<IConvention> _conventions;
// constructors
/// <summary>
/// Initializes a new instance of the <see cref="MongoDbDefaultConventionPack" /> class.
/// </summary>
private MongoDbDefaultConventionPack()
{
_conventions = new List<IConvention>
{
new ReadWriteMemberFinderConvention(),
// new NamedIdMemberConvention(new [] { "Id", "id", "_id" }), changed to:
new NamedIdMemberConvention(),
new NamedExtraElementsMemberConvention(new [] { "ExtraElements" }),
// new IgnoreExtraElementsConvention(false), changed to:
new IgnoreExtraElementsConvention(true),
new ImmutableTypeClassMapConvention(),
new NamedParameterCreatorMapConvention(),
new StringObjectIdIdGeneratorConvention(), // should be before LookupIdGeneratorConvention
new LookupIdGeneratorConvention()
};
}
// public static properties
/// <summary>
/// Gets the instance.
/// </summary>
public static IConventionPack Instance
{
get { return __defaultConventionPack; }
}
// public properties
/// <summary>
/// Gets the conventions.
/// </summary>
public IEnumerable<IConvention> Conventions
{
get { return _conventions; }
}
}
and then replaced the config:
ConventionRegistry.Remove("__defaults__");
ConventionRegistry.Register("__defaults__", MongoDbDefaultConventionPack.Instance, t => true);
Worked great in my case as default convention. No more exceptions. Original Ids available

AutoMapper - Map Derived Class To Dto

Im trying to map a Class which inherits from a base class to a dto.
public class LaunchConfiguration : Document
{
public string Brand { get; set; }
public string SettingName{ get; set; }
}
public class LaunchConfigurationDto
{
public string Brand { get; set; }
public string SettingName{ get; set; }
}
The point of the dto is to hide the fields of the base document when it gets returned to the user. This is my Map configuration
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration,LaunchConfigurationDto>();
}
};
The problem im having is that auto mapper complains about the base class properties which are not mapped . "Unmapped members were found." The properties are the ones on the base class. I have tried specifying this to be ignored in the profile to no avail . Can anyone specify the correct way to do this ?
My ConfigureServices Method incase anyone is wondering :
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = Configuration["ApiInformation:Name"], Version = Configuration["ApiInformation:Version"] });
c.DescribeAllEnumsAsStrings();
});
services.AddAutoMapper(mc =>
{
mc.AddProfile(new DtoProfile());
});
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
}
My Base Class :
public class Document : IDocument, IDocument<Guid>
{
public Document()
{
this.Id = Guid.NewGuid();
this.AddedAtUtc = DateTime.UtcNow;
}
/// <summary>The Id of the document</summary>
[BsonId]
public Guid Id { get; set; }
/// <summary>The datetime in UTC at which the document was added.</summary>
public DateTime AddedAtUtc { get; set; }
/// <summary>The version of the schema of the document</summary>
public int Version { get; set; }
}
My implementation where _mapper is my Injected mapper and _repo My Injected Repo. Exception Occurs on Map Method call
Task ILaunchConfigurationService<LaunchConfigurationDto >.InsertLaunchConfiguration(LaunchConfigurationDto model)
{
var mapped = _mapper.Map<LaunchConfiguration >(model);
return _repo.AddOneAsync(mapped);
}
Your problem should be solved by simply adding ReverseMap() to CreateMap call:
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration, LaunchConfigurationDto>().ReverseMap();
}
};
Automapper creates one way map by default. ReverseMap is just a sugar for creating reverse map in case there are no peculiar mappings in one way. You could also do it like this:
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration, LaunchConfigurationDto>();
CreateMap<LaunchConfigurationDto, LaunchConfiguration>();
}
};
You can read more about this in documentation
However I cannot guarantee you that you will not experience exceptions from database with your current implementation on commiting changes.

Categories