Description
Hi, I'm building a sample OData project with versioning in AspNet Core. And I've encountered a problem when trying to define an Action on one of my controllers.
When trying to build the swagger.json file the following Exception is thrown:
Activated Event Time Duration Thread
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware:Error: An unhandled exception has occurred while executing the request.
System.ArgumentException: Duplicate type name within an assembly.
at System.Reflection.Emit.ModuleBuilder.CheckTypeNameConflict(String strTypeName, Type enclosingType)
at System.Reflection.Emit.AssemblyBuilderData.CheckTypeNameConflict(String strTypeName, TypeBuilder enclosingType)
at System.Reflection.Emit.TypeBuilder.Init(String fullname, TypeAttributes attr, Type parent, Type[] interfaces, ModuleBuilder module, PackingSize iPackingSize, Int32 iTypeSize, TypeBuilder enclosingType)
at System.Reflection.Emit.ModuleBuilder.DefineType(String name, TypeAttributes attr)
at Microsoft.AspNet.OData.DefaultModelTypeBuilder.CreateTypeBuilderFromSignature(ModuleBuilder moduleBuilder, ClassSignature class) in E:\BA\56\s\src\Common.OData.ApiExplorer\AspNet.OData\DefaultModelTypeBuilder.cs:line 236
at Microsoft.AspNet.OData.DefaultModelTypeBuilder.NewActionParameters(IServiceProvider services, IEdmAction action, ApiVersion apiVersion, String controllerName) in E:\BA\56\s\src\Common.OData.ApiExplorer\AspNet.OData\DefaultModelTypeBuilder.cs:line 60
at Microsoft.AspNetCore.Mvc.ApiExplorer.PseudoModelBindingVisitor.CreateResult(ApiParameterDescriptionContext bindingContext, BindingSource source, String containerName) in E:\BA\56\s\src\Microsoft.AspNetCore.OData.Versioning.ApiExplorer\AspNetCore.Mvc.ApiExplorer\PseudoModelBindingVisitor.cs:line 85
at Microsoft.AspNetCore.Mvc.ApiExplorer.PseudoModelBindingVisitor.Visit(ApiParameterDescriptionContext bindingContext, BindingSource ambientSource, String containerName) in E:\BA\56\s\src\Microsoft.AspNetCore.OData.Versioning.ApiExplorer\AspNetCore.Mvc.ApiExplorer\PseudoModelBindingVisitor.cs:line 45
at Microsoft.AspNetCore.Mvc.ApiExplorer.ODataApiDescriptionProvider.GetParameters(ApiParameterContext context) in E:\BA\56\s\src\Microsoft.AspNetCore.OData.Versioning.ApiExplorer\AspNetCore.Mvc.ApiExplorer\ODataApiDescriptionProvider.cs:line 447
at Microsoft.AspNetCore.Mvc.ApiExplorer.ODataApiDescriptionProvider.NewODataApiDescriptions(ControllerActionDescriptor action, String groupName, ODataRouteMapping mapping)+MoveNext() in E:\BA\56\s\src\Microsoft.AspNetCore.OData.Versioning.ApiExplorer\AspNetCore.Mvc.ApiExplorer\ODataApiDescriptionProvider.cs:line 388
at Microsoft.AspNetCore.Mvc.ApiExplorer.ODataApiDescriptionProvider.OnProvidersExecuted(ApiDescriptionProviderContext context) in E:\BA\56\s\src\Microsoft.AspNetCore.OData.Versioning.ApiExplorer\AspNetCore.Mvc.ApiExplorer\ODataApiDescriptionProvider.cs:line 197
at Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollectionProvider.GetCollection(ActionDescriptorCollection actionDescriptors)
at Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollectionProvider.get_ApiDescriptionGroups()
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath, String[] schemes)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context) 3.75s
I declare two routes one for access by the users and another one for the services running by other components of the application.
My startup looks like this:
modelBuilder.DefaultModelConfiguration = (b, v) => UniversityModelBuilder.BuildEdmModel(b, v);
var models = modelBuilder.GetEdmModels();
app.UseMvc(builder =>
{
builder.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
builder.SetUrlKeyDelimiter(ODataUrlKeyDelimiter.Slash);
builder.MapVersionedODataRoutes("tenant", "tenant/{tenant}", models);
builder.MapVersionedODataRoutes("host", "host", models);
builder.MapRoute(name: "default",
template: "{controller=Documentation}/{action=Index}");
});
If I comment the line builder.MapVersionedODataRoutes("host", "host", models);
everything works.
The model where I declare my Action is:
if (v == Versiones.V2)
{
var sequence = modelBuilder.EntitySet<Secuencia>("Sequence").EntityType;
sequence.HasKey(x => x.Id);
sequence
.Action("AdvanceSequence")
.Returns<SequenceResult>()
.Parameter<int>("Ammount").HasDefaultValue("1");
}
And the controller is:
[ODataRoutePrefix(nameof(Secuencia)), V2]
public class SequenceController : ODataController
{
private readonly UniveristyContext _db;
private readonly DbSet<Secuencia> _dbSet;
private readonly ISequenceService _seqService;
public SequenceController(UniveristyContext db, ISequenceService seqService)
{
_db = db;
_dbSet = _db.Set<Secuencia>();
_seqService = seqService;
}
[HttpPost]
public async Task<IActionResult> AdvanceSequence([FromODataUri] string key, ODataActionParameters param)
{
int ammount = 0;
if (param != null)
{
param.TryGetValue("Ammount", out object ammountObj);
ammount = (int)ammountObj;
}
return Created(_seqService.GetNumbers(key, (int)ammount));
}
I tried to debug the problem using the source code and the problem seems to be that the method NewActionParameters is called once for each route declared, and doesn't check if the parameter was defined previously in the moduleBuilder, or use a different key for every mapping
public Type NewActionParameters( IServiceProvider services, IEdmAction action, ApiVersion apiVersion, string controllerName )
{
Arg.NotNull( services, nameof( services ) );
Arg.NotNull( action, nameof( action ) );
Arg.NotNull( apiVersion, nameof( apiVersion ) );
Arg.NotNull( controllerName, nameof( controllerName ) );
Contract.Ensures( Contract.Result<Type>() != null );
var name = controllerName + "." + action.FullName() + "Parameters";
var properties = action.Parameters.Where( p => p.Name != "bindingParameter" ).Select( p => new ClassProperty( services, p, this ) );
var signature = new ClassSignature( name, properties, apiVersion );
var moduleBuilder = modules.GetOrAdd( apiVersion, CreateModuleForApiVersion );
return CreateTypeInfoFromSignature( moduleBuilder, signature );
}
static TypeInfo CreateTypeInfoFromSignature( ModuleBuilder moduleBuilder, ClassSignature @class ) => CreateTypeBuilderFromSignature( moduleBuilder, @class ) .GetTypeInfo();
static TypeBuilder CreateTypeBuilderFromSignature( ModuleBuilder moduleBuilder, ClassSignature @class )
{
Contract.Requires( moduleBuilder != null );
Contract.Requires( @class != null );
Contract.Ensures( Contract.Result<TypeBuilder>() != null );
var typeBuilder = moduleBuilder.DefineType( @class.Name, TypeAttributes.Class );
foreach ( var attribute in @class.Attributes )
{
typeBuilder.SetCustomAttribute( attribute );
}
foreach ( var property in @class.Properties )
{
var type = property.Type;
var name = property.Name;
var propertyBuilder = AddProperty( typeBuilder, type, name );
foreach ( var attribute in property.Attributes )
{
propertyBuilder.SetCustomAttribute( attribute );
}
}
return typeBuilder;
}
Maybe changing this part can fix the issue
static TypeInfo CreateTypeInfoFromSignature( ModuleBuilder moduleBuilder, ClassSignature @class ) =>
( moduleBuilder.GetType( @class.Name, false, false ) ?? CreateTypeBuilderFromSignature( moduleBuilder, @class ) ).GetTypeInfo();