'Missing type map configuration or unsupported mapping.' Automapperexception in ASP.NET Core WebApi - asp.net-core

I'm trying to add AutoMapper to a API (built using ASP.NET Core 3) but it gives me the 'Missing type map configuration or unsupported mapping.'-exception and my google-searches doesn't help me at all... :).
The exception is thrown (as described below) in "GetAllObject1"-method
This is my current setup:
[HttpGet]
public IActionResult GetAllObject1()
{
var object1Items = _myService.GetAllObject1();
Object1ViewModel ouViewModel = _mapper.Map<Object1ViewModel>(Object1Items); // <= This line gives the exception above!!
return Ok(ouViewModel);
}
"AutoMapping.cs":
namespace DataAccess.AutoMapper
{
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap < KollOrganizationalUnit, KollOrganizationalUnitViewModel>();
}
}
}
"Startup.cs":
public virtual void ConfigureServices(IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.IgnoreNullValues = true;
});
services.AddDbContext<RepositoryContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.RegisterDAL();
services.RegisterBizServices();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "API",
Description = "Integration API for XXX",
TermsOfService = new Uri("https://www.xxx.se/terms-of-service"),
Contact = new OpenApiContact()
{
Name = "Integrationcontact",
Email = "integration#xxx.se",
Url = new Uri("https://www.xxx.se")
},
});
});
services.AddAutoMapper(typeof(Startup).Assembly);
}
Am I missing something obious here?

It's hard to say without knowing the return type of _myService.GetAllObject1(), but it seems like it's returning a collection of Object1Unit. If that's the case, you can try something like:
IEnumerable<Object1ViewModel> ouViewModels = Object1Items.Select(x => _mapper.Map<Object1ViewModel>(x));
return Ok(ouViewModels);
If you're trying to map a collection to a single item, you have to tell AutoMapper how to do that or you will get the exception you're seeing (your AutoMapping class only creates a map from a single item to a single item).

Related

Why is JobConsumer not being hit/run?

I am trying out the new MassTransit IJobConsumer implementation, and although I've tried to follow the documentation, the JobConsumer I have written is never being run/hit.
I have:
created the JobConsumer which has a run method that runs the code I need it to
public class CalculationStartRunJobConsumer : IJobConsumer<ICalculationStartRun>
{
private readonly ICalculationRunQueue runQueue;
public CalculationStartRunJobConsumer(ICalculationRunQueue runQueue)
{
this.runQueue = runQueue;
}
public Task Run(JobContext<ICalculationStartRun> context)
{
return Task.Run(
() =>
{
var longRunningJob = new LongRunningJob<ICalculationStartRun>
{
Job = context.Job,
CancellationToken = context.CancellationToken,
JobId = context.JobId,
};
runQueue.StartSpecial(longRunningJob);
},
context.CancellationToken);
}
}
I have registered that consumer trying both ConnectReceiveEndpoint and AddConsumer
Configured the ServiceInstance as shown in the documentation
services.AddMassTransit(busRegistrationConfigurator =>
{
// TODO: Get rid of this ugly if statement.
if (consumerTypes != null)
{
foreach (var consumerType in consumerTypes)
{
busRegistrationConfigurator.AddConsumer(consumerType);
}
}
if(requestClientType != null)
{
busRegistrationConfigurator.AddRequestClient(requestClientType);
}
busRegistrationConfigurator.UsingRabbitMq((context, cfg) =>
{
cfg.UseNewtonsoftJsonSerializer();
cfg.UseNewtonsoftJsonDeserializer();
cfg.ConfigureNewtonsoftJsonSerializer(settings =>
{
// The serializer by default omits fields that are set to their default value, but this causes unintended effects
settings.NullValueHandling = NullValueHandling.Include;
settings.DefaultValueHandling = DefaultValueHandling.Include;
return settings;
});
cfg.Host(
messagingHostInfo.HostAddress,
hostConfigurator =>
{
hostConfigurator.Username(messagingHostInfo.UserName);
hostConfigurator.Password(messagingHostInfo.Password);
});
cfg.ServiceInstance(instance =>
{
instance.ConfigureJobServiceEndpoints(serviceCfg =>
{
serviceCfg.FinalizeCompleted = true;
});
instance.ConfigureEndpoints(context);
});
});
});
Seen that the queue for the job does appear in the queue for RabbitMQ
When I call .Send to send a message to that queue, it does not activate the Run method on the JobConsumer.
public async Task Send<T>(string queueName, T message) where T : class
{
var endpointUri = GetEndpointUri(messagingHostInfo.HostAddress, queueName);
var sendEndpoint = await bus.GetSendEndpoint(endpointUri);
await sendEndpoint.Send(message);
}
Can anyone help?
Software
MassTransit 8.0.2
MassTransit.RabbitMq 8.0.2
MassTransit.NewtonsoftJson 8.0.2
.NET6
Using in-memory for JobConsumer
The setup of any type of repository for long running jobs is missing. We needed to either:
explicitly specify that it was using InMemory (missing from the docs)
Setup saga repositories using e.g. EF Core.
As recommended by MassTransit, we went with the option of setting up saga repositories by implementing databases and interacting with them using EF Core.

How to configure Swashbuckle to ignore property on model for a specific api version only

I needed to add a property to a model and I have implemented what is suggested in the selected answer here and it's working in that it removes the property I have tagged with SwaggerIgnorePropertyAttribute attribute of the model.
My question is , if I have several API versions of my application,how to remove it from the swagger doc/schema for certain versions? I only care not to see the added property in swagger. I only really have 1 version of the model across the application even though I have several version of the app.
I added the version like this:
services.AddSwaggerGen(
swaggerOptions =>
{
swaggerOptions.SwaggerDoc(
"v1",
new Info
{
Title = "Titlebla1",
Description = "bla1",
Version = "v1"
});
swaggerOptions.SwaggerDoc(
"v2",
new Info
{
Title = "Titlebla2",
Description = "bla2",
Version = "v2"
});
etc
I know that I can find the version by using SwaggerDocument and doing something like this: swaggerDoc.Info.Version but how can I get access to SwaggerDocument from the Apply in my SwaggerExcludePropertySchemaFilter class below ?
private class SwaggerExcludePropertySchemaFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema?.Properties == null)
{
return;
}
var excludedProperties = context.SystemType.GetProperties().Where(t => t.GetCustomAttribute<SwaggerIgnorePropertyAttribute>() != null);
foreach (var excludedProperty in excludedProperties)
{
var propertyToRemove = schema.Properties.Keys.SingleOrDefault(x => string.Equals(x, excludedProperty.Name, StringComparison.OrdinalIgnoreCase));
if (propertyToRemove != null)
{
schema.Properties.Remove(propertyToRemove);
}
}
}
}
Try use IDocumentFilter , see example:
public class CustomSwaggerFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var nonRequiredMYPropertyAPIs = swaggerDoc.Paths
.Where(x => !x.Key.ToLower().Contains("v1") /*the version you want to remove the property */)
.ToList();
nonRequiredMYPropertyAPIs.ForEach(x => {
swaggerDoc.Components.Schemas["YOUR_CLASS_MODEL"]
.Properties.Remove("PROPERTY_NAME_YOU_WANT_TO_IGNORE");
});
}
}
Don't forget register the filter:
c.DocumentFilter<CustomSwaggerFilter>();

Unable to create multiple OpenApi specifications with Swashbuckle

I am building a solution with Asp.Net Boilerplate / Asp.Net Zero
I have created two OpenApi specifications (HostApiv1 and TenantApiv1) as follows in Startup.cs:
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("HostApiv1", new Info { Title = "Host API v1", Version = "v1" });
options.SwaggerDoc("TenantApiv1", new Info { Title = "Tenant API v1", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.IgnoreObsoleteActions();
options.IgnoreObsoleteProperties();
options.OrderActionsBy((apiDesc) => $"{apiDesc.RelativePath}");
options.DescribeAllEnumsAsStrings();
});
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint(_appConfiguration["App:HostApiSwaggerEndPoint"], "Host API v1");
options.SwaggerEndpoint(_appConfiguration["App:TenantApiSwaggerEndPoint"], "Tenant API v1");
//...
});
However, when I decorate my AppService classes with [ApiExplorerSettings(GroupName = "HostApiv1")], the grouping is ignored and the tag (AppService controller), along with all of its operations (actions / methods), still appear under both documents.
Any idea what is wrong, or how I can debug it?
Swashbuckle depends on ApiExplorer, and the use of the ApiExplorer attribute limits us to specifying only a single groupname per controller / action. ABP service proxies are generated via NSwag for angular project, and it seems that during this process a dependency is broken.
The workaround is to create a custom Attribute for delimiting one-or-more groupnames for an appservice controller or action, and subsequently use reflection in the DocInclusionPredicateFunction option to retrieve the groupnames for an action or its containing controller.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class SwaggerDocAttribute: Attribute
{
public SwaggerDocAttribute(params string[] includeInDocuments)
{
IncludeInDocuments = includeInDocuments;
}
public string[] IncludeInDocuments { get; }
}
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (!apiDesc.ActionDescriptor.IsControllerAction())
{
return false;
}
apiDesc.TryGetMethodInfo(out MethodInfo methodInfo);
var actionDocs = methodInfo.GetCustomAttributes<SwaggerDocAttribute>()
.SelectMany(a => a.IncludeInDocuments);
var controllerDocs = methodInfo.DeclaringType.GetCustomAttributes<SwaggerDocAttribute>()
.SelectMany(a => a.IncludeInDocuments);
switch (docName)
{
case "HostApiv1":
return apiDesc.GroupName == null ||
actionDocs.Contains("HostApiv1") ||
controllerDocs.Contains("HostApiv1");
case "TenantApiv1":
return apiDesc.GroupName == null ||
actionDocs.Contains("TenantApiv1") ||
controllerDocs.Contains("TenantApiv1");
default:
return true;
}
});
Usage
[DisableAuditing]
[AbpAuthorize(AppPermissions.HostSpecific.Dashboard.Access)]
//[ApiExplorerSettings(GroupName = "HostApiv1")] // <== Don't use this
[SwaggerDoc("HostApiv1")] // <== Use this in stead
public class MyDemoAppService : ZenDetectAppServiceBase, IHostDashboardAppService
{
//...
}

Using Custom Entities with OpenIddict

I'm trying to extend the entities used by OpenIddict based on the example provided here:
https://github.com/openiddict/openiddict-core/issues/360#issuecomment-280268525
This includes creating entity classes extending those given by OpenIddict:
public class CustomApplication : OpenIddictApplication<Guid, CustomAuthorization, CustomToken>
{
public string ImpersonateUser { get; set; }
}
public class CustomAuthorization : OpenIddictAuthorization<Guid, CustomApplication, CustomToken>
{
}
public class CustomScope : OpenIddictScope<Guid>
{
}
public class CustomToken : OpenIddictToken<Guid, CustomApplication, CustomAuthorization>
{
}
Updating AddDbContext:
services.AddDbContext<ApplicationContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ConnectionString"));
options.UseOpenIddict<CustomApplication, CustomAuthorization, CustomScope, CustomToken, Guid>();
});
And a modified version of the call to register the services, to match the newer API:
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore().UseDbContext<ApplicationContext>()
.ReplaceDefaultEntities<CustomApplication, CustomAuthorization, CustomScope, CustomToken, Guid>();
})
Everything appears fine up to this point. Then, as part of application startup, I check for an existing client, and create it if it's not found:
var manager = serviceScope.ServiceProvider.GetRequiredService<OpenIddictApplicationManager<CustomApplication>>();
if (await manager.FindByClientIdAsync("Application") == null) // Throws Here
{
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "Application",
ClientSecret = "application-default-secret",
DisplayName = "Application",
RedirectUris = { new Uri("http://localhost:5000/signin-oidc")},
Permissions = { OpenIddictConstants.Permissions.Endpoints.Authorization,
OpenIddictConstants.Permissions.Endpoints.Logout,
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
OpenIddictConstants.Permissions.Scopes.Email,
OpenIddictConstants.Permissions.Scopes.Profile,
OpenIddictConstants.Permissions.Scopes.Roles}
};
await manager.CreateAsync(descriptor);
}
As noted by the "Throws Here" comment, an exception is thrown which is a SqlException:
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
Failed executing DbCommand (9ms) [Parameters=[#__identifier='Application' (Size = 100)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [application].[Id], [application].[ClientId], [application].[ClientSecret], [application].[ConcurrencyToken], [application].[ConsentType], [application].[DisplayName], [application].[ImpersonateUser], [application].[Permissions], [application].[PostLogoutRedirectUris], [application].[Properties], [application].[RedirectUris], [application].[Type]
FROM [OpenIddictApplications] AS [application]
WHERE [application].[ClientId] = #__identifier
System.Data.SqlClient.SqlException (0x80131904): Invalid column name 'ImpersonateUser'.
It appears somewhere along the way, it's trying to query the old tables instead of the newer ones that I'm specifying. The only things on the callstack for OpenIddict between my seed method, and the SQL exception are OpenIddict.Core.OpenIddictApplicationManager`1.FindByClientIdAsync(String identifier, CancellationToken cancellationToken) and OpenIddict.Core.OpenIddictApplicationCache`1.<>c__DisplayClass6_0.<<FindByClientIdAsync>g__ExecuteAsync|0>d.MoveNext()

Swagger 2.0 with API 2.o and Odata 3.0

I been trying to implement swagger, through [Swashbuckle][1] on my application, but i get no endpoints at all on my swagger ui, and my doc just returns this
{
"swagger": "2.0",
"info": {
"version": "v1",
"title": "NB.EAM.WebAPI.V4"
},
"host": "localhost:24320",
"schemes": [
"http"
],
"paths": {},
"definitions": {}
}
In my webApiConfig i set the following configuration from following the dummys
var swagConfig = new HttpSelfHostConfiguration("http://localhost:24320");
SwaggerConfig.Register();
WebApiConfig.Register(swagConfig);
using (var server = new HttpSelfHostServer(swagConfig))
{
server.OpenAsync().Wait();
}
My swagger configuration is the standart one created by Swashbuckle:
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "NB.EAM.WebAPI.Odata");
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
var commentsFileName = Assembly.GetExecutingAssembly().GetName().Name + ".XML";
var commentsFile = Path.Combine(baseDirectory, "bin", commentsFileName);
c.IncludeXmlComments(commentsFile);
c.DocumentFilter<ApplyResourceDocumentation>();
c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c, GlobalConfiguration.Configuration).Configure(odataConfig =>
{
odataConfig.IncludeNavigationProperties();
}));
})
.EnableSwaggerUi(c =>
{
});
Any idea what i might be missing?
Edit:
here is more information about my setting
full code of my WebApiConfig:
public static void Register(HttpConfiguration config)
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor()));
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
var conventions = ODataRoutingConventions.CreateDefault();
conventions.Insert(0, new CompositeKeyRoutingConvention());
conventions.Insert(1, new CompositeKeyNavigationRoutingConvention());
conventions.Insert(2, new CountODataRoutingConvention());
ODataBatchHandler batchHandler = new UnitOfWorkBatchHandler(GlobalConfiguration.DefaultServer);
config.Routes.MapODataServiceRoute("odata", "odata", GenerateEdmModel(), new CountODataPathHandler(), conventions, batchHandler);
config.Filters.Add(new SqlExceptionFilterAttribute());
config.Filters.Add(new FilterInterceptor());
InitContentRepository();
log4net.Config.XmlConfigurator.Configure();
var swagConfig = new HttpSelfHostConfiguration("http://localhost:24320");
SwaggerConfig.Register();
WebApiConfig.Register(swagConfig);
using (var server = new HttpSelfHostServer(swagConfig))
{
server.OpenAsync().Wait();
}
}
public static Microsoft.Data.Edm.IEdmModel GenerateEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.ContainerName = "NBContext";
builder.EntitySet<as_portfolio>("as_portfolio");
builder.EntitySet<cf_usersportfolio>("cf_usersportfolio");
builder.EntitySet<as_locatportfolio>("as_locatportfolio");
builder.EntitySet<ac_bdgaset>("ac_bdgaset");
builder.EntitySet<ac_bdgcc>("ac_bdgcc");
builder.EntitySet<ac_bdgdtl>("ac_bdgdtl");
builder.EntitySet<ac_bdgloca>("ac_bdgloca");
builder.EntitySet<ac_bdgress>("ac_bdgress");
builder.EntitySet<ac_bdgsect>("ac_bdgsect");
builder.EntitySet<ac_bdgwoa>("ac_bdgwoa");
builder.EntitySet<ac_bdgwob>("ac_bdgwob");
builder.EntitySet<ac_bdgwolb>("ac_bdgwolb");
builder.EntitySet<ac_bdgwost>("ac_bdgwost");
builder.EntitySet<ac_bgdcc>("ac_bgdcc");
builder.EntitySet<ac_custome>("ac_custome");
----Very long list of enetitySets
return builder.GetEdmModel();
}
An example of my API
using NB.EAM.DataV2;
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.OData;
namespace NB.EAM.WebAPI.Controllers
{
public class wo_hrtypeController : BaseODataController
{
// GET: odata/wo_hrtype
[Queryable]
public IQueryable<wo_hrtype> Getwo_hrtype()
{
return this.GetWo_HrtypeBll.GetAll();
}
// GET: odata/wo_hrtype(5)
[Queryable]
public SingleResult<wo_hrtype> Getwo_hrtype([FromODataUri] string key)
{
return SingleResult.Create(this.GetWo_HrtypeBll.Find(wo_hrtype =>
wo_hrtype.lb_tyhr == key));
}
}
There is no much information to work there.
We don't know what kind of filters are being aplied on ApplyResourceDocumentation (actually, this class is on the swashbuckle.odata sample proyect and may not fit your necesities:
https://github.com/rbeauchamp/Swashbuckle.OData/blob/master/Swashbuckle.OData.Sample/DocumentFilters/ApplyResourceDocumentation.cs).
We can't also check your entities and function definitions. Check this as an example: https://github.com/rbeauchamp/Swashbuckle.OData/blob/master/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs
And we can't also check if your controllers are defined in a proper way (Methods as verbs. I think custom named methods are only taken into account if they are defined as functions)
I think I just had a similar issue. Try replacing the following line
GlobalConfiguration.Configuration.EnableSwagger(...
with this one:
swagConfig.EnableSwagger(...
The thing is that you should use here the same configuration instance that you pass to the HttpSelfHostServer constructor.
SwaggerConfig.Register(swagConfig); // pass the swagConfig instance to the auto-generated method
WebApiConfig.Register(swagConfig);
using (var server = new HttpSelfHostServer(swagConfig))
{
server.OpenAsync().Wait();
}