Swagger different classes in different namespaces with same name don't work - asp.net

I got (more than) two Api POST endpoints. Each one needs a json as parameter. But when I use the same class name Payload in two endpoint argument classes, Swagger does not work. When I change one of it e.g. from Payload to Payload1 than it works.
Of course I set the right namespaces into the wrapper classes so it finds it Payload. But I would love to use the same name "Payload" each time. How can I use the same class name Payload?
I can keep the json name "Payload" at both cases and just set different names for the property ("Payload1", "Payload2"). It works. But would be nice to have same property names too.,
Endpoint A
[HttpPost()]
public async Task PostPerson([FromBody]JsonWrapperA jsonWrapperA)
namespace myProject.A
{
public class JsonWrapperA
{
[JsonProperty("name", Required = Required.AllowNull)]
public string Name { get; set; }
[JsonProperty("payload", Required = Required.AllowNull)]
public Payload Payload { get; set; }
}
public class Payload
{
[JsonProperty("value", Required = Required.AllowNull)]
public double Value { get; set; }
}
}
Endpoint B
[HttpPost()]
public async Task PostCompagn([FromBody]JsonWrapperB jsonWrapperB)
namespace myProject.B
{
public class JsonWrapperB
{
[JsonProperty("compagny", Required = Required.AllowNull)]
public string Compagny { get; set; }
[JsonProperty("payload", Required = Required.AllowNull)]
public Payload Payload { get; set; }
}
public class Payload
{
[JsonProperty("age", Required = Required.AllowNull)]
public double Age{ get; set; }
}
}

By default swagger will attempt to build its Schema Ids for objects that are return types or parameter types for your APIs endpoints, and it will display these objects in the "Models" section of the documentation. It will build these schema Ids based on the class names of the objects.
When you try to have two or more classes named the same, even though they are in different namespaces, then you will get the conflicting schemaIds error:
InvalidOperationException: Conflicting schemaIds: Identical schemaIds detected for types NamespaceOne.MyClass and NamespaceTwo.MyClass. See config settings - "CustomSchemaIds" for a workaround
This means Swagger needs to be configured to change the way it generates its SchemaIds. You can simply tell swagger to use an objects fully qualified name which will include namespaces in the schemaIds. You can do this in your Startup.cs file in the ConfigureServices method like this:
//add using statement for Swagger in Startup.cs
using Swashbuckle.AspNetCore.Swagger;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(config =>
{
//some swagger configuration code.
//use fully qualified object names
config.CustomSchemaIds(x => x.FullName);
}
}

Using Swashbuckle.AspNetCore Version 5.5.1 i've had the same issue so i solved it using JustSomeDude answer, but afterwards all entities were shown with the full name so i needed a way to show only the name. This is what i did:
options.CustomSchemaIds(x => x.FullName); // Enables to support different classes with the same name using the full name with namespace
options.SchemaFilter<NamespaceSchemaFilter>(); // Makes the namespaces hidden for the schemas
Using this filter class:
public class NamespaceSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema is null)
{
throw new System.ArgumentNullException(nameof(schema));
}
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
schema.Title = context.Type.Name; // To replace the full name with namespace with the class name only
}
}

Related

Swagger UI doesn't render body parameter field for my complex type parameter in GET action of my Controller

I have an ASP.NET Web API 2 project to which I have added Swagger - Swashbuckle v5.6.0. Everything works fine. Swagger UI renders test endpoints for my API as expected.
I added a new Controller to my API. There is a GET action with a complex type parameter. For complex types, Web API tries to read the value from the message body. This is the default behaviour.
Here is my GET action:
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok();
}
And her is my complex type:
public class SearchModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string Telephone { get; set; }
public string MobilePhone { get; set; }
}
The problem:
But Swagger UI doesn't render body parameter field for my complex type in the GET action. For POST and PUT actions Swagger UI renders body parameter fields as expected but not for the complex type in my GET action.
As can be seen in the screenshot Swagger UI renders query parameters fields for attributes in my complex type instead of rendering a body parameter field for my type as it does in the case of POST and PUT.
My GET action is working fine when testing from Postman and filling the json in the body of the request. By setting breakpoint in the action inside Visual Studio I can see the values are bound to my object in the action parameter.
I have tried to decorate the parameter in my action with [FromBody] (which is the default for complex type) but same result.
Is this a bug in Swagger? Or am I missing something?
Sadly, you can't do what you want with Swagger. You can't send a request model in an HTTP GET method. You can however change the swagger UI to look like this:
but you won't be able to receive the model in your controller.
This is a known issue within the Swagger developers and it was discussed in 2016 and the final decision is that swagger won't support a request body in an HTTP GET method. Here is the link to the already closed issue.
You have three options here:
Leave the method as it is, and test it in Postman, but not in Swagger.
Follow the below steps to achieve the picture above, but please note, that it will only fix the UI part and you will always end up with null SearchModel in the controller when you press Try it out! in swagger.
Make it a [HttpPost method instead of [HttpGet].
How to make swagger UI display GET method with request body:
First, create one Attribute class:
public class ModelInBodyAttribute : Attribute
{
public ModelInBodyAttribute(string modelName, string description, bool isRequired)
{
this.ModelName = modelName;
this.Description = description;
this.IsRequired = IsRequired;
}
public string ModelName { get; set; }
public bool IsRequired { get; set; }
public string Description { get; set; }
}
Then you can decorate your method in the controller:
[ModelInBody(modelName: nameof(SearchModel), description: "My model description", isRequired: true)]
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok(new List<SearchModel>());
}
After that create IOperationFilter class (ModelInBodyOperationFilter):
public class ModelInBodyOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var attribute = apiDescription.GetControllerAndActionAttributes<ModelInBodyAttribute>().FirstOrDefault();
if (attribute == null)
{
return;
}
operation.parameters.Clear();
operation.parameters.Add(new Parameter
{
name = attribute.ModelName,
description = attribute.Description,
#in = "body",
required = attribute.IsRequired,
schema = new Schema { #ref = $"#/definitions/{attribute.ModelName}" }
});
}
}
Lastly, don't forget to register the IOperationFilter in SwaggerConfig:
c.OperationFilter<ModelInBodyOperationFilter>();
When you send the request via swagger, you will notice that the Curl part is absolutely correct, but still, in your controller there is nothing.
There are endless discussions on whether you should have a PAYLOAD "Body content" in a GET request. As you mentioned it's supported by HTTP but you will find in the internet that many people suggest not to do it. I guess that swagger team also expect you not to use it.

MassTransit. Consume equal objects defined in different namespaces

First of all, excuse my English, it's very bad. I am using MassTransit with Azure Service Bus for asynchronous communication between microservices. By their own definition, and to avoid generating dependencies between them, messages sent between different microservices are defined in each of them, that is, they are part of different namespaces. The automatic management of MassTransit causes queues and topics to be managed by the object type, which prevents the microservices that consume a message from receiving the messages sent by the microservice publisher. The same thing happens with two classes with the same properties in the same namespace but with a different class name.
Is there any way to solve this?
The options that have occurred to me are:
Remove the namespace from the endpoint of the destination address, naming it only with the name of the class.
That MassTransit can manage the creation of queues and topics based on the serialization of the object, instead of managing it based on the object type (perhaps through some type of wrapping object?)
I leave an example that I hope can help you in understanding the problem.
//FIRST PROGRAM - MESSAGE CONSUMER
namespace Consumer
{
public class Example
{
public string PropOne { get; set; }
public string PropTwo { get; set; }
}
public class ExampleConsumer :
IConsumer<Example>
{
public List<Example> ConsumedTestObjectList { get; } = new List<Example>();
//THIS METHOD NEVER CALL !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
public Task Consume(ConsumeContext<ExampleConsumer> context)
{
ConsumedTestObjectList.Add(context.Message);
return Task.CompletedTask;
}
}
public class ConsumerProgram
{
public static void Main()
{
var bus = Bus.Factory.CreateUsingAzureServiceBus(sbc =>
{
var host = sbc.Host("connectionString", h => {});
});
sbc.ReceiveEndpoint(host, e =>
{
e.Consumer<ConsumerProgram.Example>(context =>
{
return Console.Out.WriteLineAsync($"Message Received: {JsonConvert.SerializeObject(context.Message)}");
});
});
bus.Start(); // This is important!
Console.WriteLine("Press any key to exit");
Console.ReadKey();
bus.Stop();
}
}
}
//SECOND PROGRAM - MESSAGE PUBLISHER
namespace Publisher
{
public class Example
{
public string PropOne { get; set; }
public string PropTwo { get; set; }
}
public class PublisherProgram
{
public static void Main()
{
var bus = Bus.Factory.CreateUsingAzureServiceBus(sbc =>
{
var host = sbc.Host("connectionString", h => {});
});
bus.Start(); // This is important!
//send new instance of Publisher.Example
var example = new Example() { PropOne = "1", PropTwo = "2" };
bus.Publish(example);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
bus.Stop();
}
}
}
Thank you very much.
regards
Borja
The message type, and the resulting name, are a key concept within MassTransit. If you want to avoid sharing assemblies between projects, that is fine, but you will need to match the entire interface (or class, in your case) name, including namespace, or it will not route properly.
Yes, you can override the entity name formatter to change how topics are named but it won't change the message type requirement for deserialization of the message (which happens, by type).
So the recommendation here is to use the same namespace for the contracts, even if they're in separate projects.

How to specify default property values for owned entity types in Entity Framework Core 2.0?

I have a simple POCO type, say something like
public class OwnedEntity {
public string stringProperty { get; set; }
public decimal decimalProperty { get; set; }
public bool boolProperty { get; set; }
public int intProperty { get; set; }
}
and an actual entity with an OwnedEntity reference
public class SomeEntity {
public string Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
}
I set up the relationship like described in the documentation using EF Core's Fluent API:
protected override void OnModelCreating (ModelBuilder builder) {
base.OnModelCreating (builder);
builder.Entity<SomeEntity> ().OwnsOne (e => e.OwnedEntity);
}
I can't find anything on how to define default-values for all the properties of OwnedEntity. I tried to initialize the properties like this:
public class OwnedEntity {
public string stringProperty { get; set; } = "initial"
public decimal decimalProperty { get; set; } = -1M;
public bool boolProperty { get; set; } = false;
public int intProperty { get; set; } = -1;
}
but with no effect. Same goes with the [DefaultValueAttribute] (but that was to expect since it's explicitly mentioned).
There's a bit of information on how to handle initial values for regular entities:
modelBuilder.Entity<SomeOtherEntity>()
.Property(e => e.SomeIntProperty)
.HasDefaultValue(3);
But since I'm facing an Owned Entity Type, I can't access the type via Entity<T>.
Is there a way of doing what I'm looking for?
Some things worth mentioning:
I have a solid amount of specific entities where most of them are using the OwnsOne relation
Declaring all OwnedEntity-properties in a base class is not an option since not all the entities have those properties
I`m using EF Core 2.0.3 and ASP.NET Core MVC 2.0.4
Edit:
Originally, I wanted to have newly created SomeEntity instances to come with preset properties for all of the 'embedded' SomeEntity.OwnedEntity properties.
But looking at how my associated controller works, it all makes sense... I have the following methods for the 'Create' operation:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create (SomeEntity model) {
context.Add (model);
await context.SaveChangesAsync ();
// redirect etc.
}
Which means that no object is created for the [HttGet] overload of Create and all the HTML inputs linked to properties (via asp-for) are initially empty. Okay. So I guess the proper way of doing this is to manually create a new instance of SomeEntity and pass it to the Create view like this:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create), new SomeEntity());
}
Is this the right approach then or are there some more things to keep in mind?
Assuming you understand what EF Core Default Values are for, and just looking for equivalent of Entity<T>().Property(...) equivalent.
The owned entities are always configured for each owner type by using the ReferenceOwnershipBuilder<TEntity,TRelatedEntity> class methods. To access this class you either use the result of OwnsOne method, or use the OwnsOne overload taking second argument of type Action<ReferenceOwnershipBuilder<TEntity,TRelatedEntity>>.
For instance, using the second approach:
builder.Entity<SomeEntity>().OwnsOne(e => e.OwnedEntity, ob =>
{
ob.Property(e => e.stringProperty)
.HasDefaultValue("initial");
ob.Property(e => e.decimalProperty)
.HasDefaultValue(-1M);
// etc.
});

.Net Core Api Controller inheritance

I'm trying to create an API Module that can be used across multiple API projects. I want the Module to have a controller with an action that can be overriden based on the implementing API requirements.
I have the following project structure:
Module
-Controllers
-ModuleController.cs
-Interfaces
-IModel.cs
-Models
-Model.cs
API
-Controllers
-DerivedModuleController.cs
IModel.cs
public interface IModel
{
int Id { get; set; }
}
Model.cs
public class Model : IModel
{
public int Id { get; set; }
}
ModuleController.cs
[Route("api/module/[action]")]
public class ModuleController : Controller
{
[ActionName("GetModel")]
[HttpGet("{id}")]
public virtual Model GetModel(int id)
{
return new Model() { Id = id };
}
}
DerivedModuleController.cs
public class DerivedModuleController : ModuleController
{
public override Model GetModel(int id)
{
return base.GetModel(id);
}
}
The API project references the Module. If I remove the DerivedModuleController everything works fine. I can make a request to /api/module/GetModel/1 and get a valid result. However, when I add the DerivedModuleController it fails with the following error:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
Api.Controllers.DerivedModuleController.GetModel (Api)
Module.ModuleController.GetModel (Module)
Ultimately I want to be able to get a DerivedModel that extends Model from the DerivedModuleController but I can't get past this error.
Is there a way to use the base route (/api/module/GetModel/1) to access the DerivedModuleController and ignore the ModuleController?

OData paging with expand issue

I'm using OData v5/Web API 2.2 to create an endpoint that will return a list of employees from each company.
My problem occurs when I try to implement server-side paging while also using the OData $expand property. When I try to make a call to
http://localhost:60067/Companies?$expand=Employees
I get an error that says "Could not find a property named 'Employees' on type 'System.Web.OData.Query.Expressions.SelectAllAndExpand_1OfCompanyApiModel'"
However, when I removed the EnableQuery attribute the call to the endpoint or when I didn't expand it works as expected. Does anyone have an idea of what I am doing wrong? I've been googling this for a while but haven't found anything.
Here are some code snippets -
Data Models:
public class CompanyApiModel
{
[Key]
public Guid CompanyGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
public List<EmployeeApiModel> Employees { get; set; }
}
public class EmployeeApiModel
{
[Key]
public Guid EmployeeGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
}
CompaniesController.cs:
[EnableQuery(PageSize = 10)] // If I comment this out everything works
//[EnableQuery] // This fails as well
public IHttpActionResult Get(ODataQueryOptions<CompanyApiModel> queryOptions)
{
var companies = GetCompanies(queryOptions);
return Ok(companies);
// return Ok(companies.AsQueryable()); // This doesn't work either
}
WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
var routingConventions = ODataRoutingConventions.CreateDefault();
routingConventions.Insert(0, new OptionsRoutingConvention());
config.MapODataServiceRoute("odata", null, GetEdmModel(), new DefaultODataPathHandler(), routingConventions);
// below code allows endpoints to respond with either XML or JSON, depending on accept header preferences sent from client
// (default in absence of accept header is JSON)
var odataFormatters = ODataMediaTypeFormatters.Create();
config.Formatters.InsertRange(0, odataFormatters);
config.EnsureInitialized();
}
public static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "Demos";
builder.ContainerName = "DefaultContainer";
builder.EntitySet<CompanyApiModel>("Companies");
builder.EntitySet<EmployeeApiModel>("Employees");
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
Figured out the problem. We were overriding the EnableQuery attribute somewhere in our code and calling it EnableMappedQuery and applying it to the controller. Thus instead of having [EnableQuery(PageSize = 10)] I should have had [EnableMappedQuery(PageSize = 10)].
EnableQuery Attribute do many works,
1. it will validate the queryoption for you.
2. it will apply the queryoption for you.
3. it can add some querysettings like PageSize.
Your scenario not working is because your GetCompanies is already applied the queryoption, so when EnableQuery get the result and apply the queryoption again, it fails, it can't find the expand property, my suggestion is just return original Company and let EnableQuery do the reset of work for you, ODataQueryOption in parameter is also not needed.
If you realy do some custom work in GetCompanies and don't need EnableQuery to apply for you, you can add PageSize in ODataQuerySettings when you call method ODataQueryOptions.ApplyTo(IQueryable, ODataQuerySettings).

Resources