I'm using:
AutoMapper 6.1.1
AutoMapper.Extensions.Microsoft.DependencyInjection
3.0.1
It seems my profiles are not being loaded, every time I call the mapper.map I get AutoMapper.AutoMapperMappingException: 'Missing type map configuration or unsupported mapping.'
Here my Startup.cs class ConfigureServices method
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//register automapper
services.AddAutoMapper();
.
.
}
In another project called xxxMappings I have my mapping profiles.
Example class
public class StatusMappingProfile : Profile
{
public StatusMappingProfile()
{
CreateMap<Status, StatusDTO>()
.ForMember(t => t.Id, s => s.MapFrom(d => d.Id))
.ForMember(t => t.Title, s => s.MapFrom(d => d.Name))
.ForMember(t => t.Color, s => s.MapFrom(d => d.Color));
}
public override string ProfileName
{
get { return this.GetType().Name; }
}
}
And call the map this way in a service class
public StatusDTO GetById(int statusId)
{
var status = statusRepository.GetById(statusId);
return mapper.Map<Status, StatusDTO>(status); //map exception here
}
status has values after calling statusRepository.GetById
For my Profile classes, if instead of inherit from Profile I inherit from MapperConfigurationExpression I got a unit test like the one below saying the mapping is good.
[Fact]
public void TestStatusMapping()
{
var mappingProfile = new StatusMappingProfile();
var config = new MapperConfiguration(mappingProfile);
var mapper = new AutoMapper.Mapper(config);
(mapper as IMapper).ConfigurationProvider.AssertConfigurationIsValid();
}
My guess is that my mappings are not being initialized.
How can I check that? Am I missing something?
I saw an overload for AddAutoMapper() method
services.AddAutoMapper(params Assembly[] assemblies)
Should I pass all the assemblies in my xxxMappings project. How can I do that?
I figure it out. Since my mappings are in a different project, I did two things
From my API project (where Startup.cs is located, added a reference to my xxxMapprings project)
in ConfigureServices I used the overload AddAutoMapper that gets an Assembly:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//register automapper
services.AddAutoMapper(Assembly.GetAssembly(typeof(StatusMappingProfile))); //If you have other mapping profiles defined, that profiles will be loaded too.
Another solution for fixing the issue when we have mapping profiles in different projects in a solution is:
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
1. Create AutoMapperProfile inherit Profile Class
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
ConfigureMappings();
}
private void ConfigureMappings()
{
// DriverModel and Driver mapping classes
CreateMap<DriverModel, Driver>().ReverseMap();
}
}
2. Register this profile in Configuration service
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(AutoMapperProfile));
}
Related
When I was using Automapper v6 (I'm using .net core), I had this command to validate configuration :
configuration.AssertConfigurationIsValid();
But now, after moving to the latest version, I don't have this since my config is exactly (docs):
private void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(AppSettingsMappingProfile)); //marker type
}
However, I still want to validate all mappings at startup .
The docs says that I need to do this :
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap<Source, Destination>());
configuration.AssertConfigurationIsValid();
But I don't have it since I'm using profiles with this command :
services.AddAutoMapper(typeof(AppSettingsMappingProfile));
Question:
How can I still make AutoMapper scan for validation at startup?
If you look at the source code for AddAutoMapper, you will see that it registers IConfigurationProvider as singleton. This means you can safely have it in your Configure method and do the validation there:
public void Configure(IConfigurationProvider pr)
{
pr.AssertConfigurationIsValid();
}
Following these steps should work:
Add AutoMapper.Extensions.Microsoft.DependencyInjection NuGet
package
services.AddAutoMapper(typeof(...)) within
ConfigureServices(...).
Add IMapper mapper as parameter to Configure(...) method
mapper.ConfigurationProvider.AssertConfigurationIsValid(); within
Configure(...)
Example (omitting namespace inclusion)
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(Startup));
}
public void Configure(IApplicationBuilder app, IMapper mapper)
{
mapper.ConfigurationProvider.AssertConfigurationIsValid();
}
I have an asp.net core 3.1 web application
I have an Interface which is implemented by 3 classes to configure database mapping. I want to call the method automatically during application configuration setup.
Following is my interface and their implementation.
public interface IMongoMapper
{
void Configure();
}
class TenantMap : IMongoMapper
{
public void Configure()
{
BsonClassMap.RegisterClassMap<Entities.Tenant>(cm =>
{
cm.AutoMap();
});
}
}
class CourseMap : IMongoMapper
{
public void Configure()
{
BsonClassMap.RegisterClassMap<Course>(cm =>
{
cm.AutoMap();
});
}
}
How to get all the classes that implement interface and call Configure
method appropriately?
You can use scope.ServiceProvider.GetServices<IMongoMapper>(); to get all classes that implement IMongoMapper interface.
You can use an extension method and call it in Configure method in startup class.
public static void IntializeMapping(this IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var mappers = scope.ServiceProvider.GetServices<IMongoMapper>();
foreach (var map in mappers)
{
map.Configure();
}
}
}
and use it in startup class
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.IntializeMapping();
}
Update
According to Microsoft documentation better way is use this
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
try
{
var mappers = scope.ServiceProvider.GetServices<IMongoMapper>();
foreach (var map in mappers)
{
map.Configure();
}
}
catch (Exception ex)
{
var logger = service.GetService<ILogger<Program>>();
logger.LogError(ex, "An error occurred mapping");
}
}
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Microsoft Documentation
In older tutorials, you may see similar code in the Configure method in Startup.cs. We recommend that you use the Configure method only to set up the request pipeline. Application startup code belongs in the Main method.
Now the first time you run the application, the database will be created and seeded with test data. Whenever you change your data model, you can delete the database, update your seed method, and start afresh with a new database the same way. In later tutorials, you'll see how to modify the database when the data model changes, without deleting and re-creating it.
Assuming that you have empty constructor for the derived classes as mentioned in your example,you can do the below code you can get the interface by reflection and check which type can be assignable and !c.IsInterface so as it doesn't return the interface itself:
var result = typeof("Any Class").Assembly.GetTypes().Where(c => typeof(IMongoMapper).IsAssignableFrom(c) && !c.IsInterface);
foreach (var i in result)
{
((IMongoMapper)Activator.CreateInstance(i)).Configure();
}
You can do it via Reflection.
I just tried the code below and it works in dotnet core 3.1.
It works with the interface and the implementation class:
in the same assembly
in separate assemblies
var asm = Assembly.GetAssembly(typeof(YourClass));
var mapperTypes = asm.GetTypes().Where(x => x.GetInterface(nameof(IMongoMapper)) != null);
foreach(var mapperType in mapperTypes)
{
var mapper = (IMongoMapper)Activator.CreateInstance(mapperType);
mapper.Configure();
}
You can also plug any parameters you need to create an instance of your objects:
IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
//other code
foreach(var mapperType in mapperTypes)
{
var mapper = (IMongoMapper)Activator.CreateInstance(mapperType, _configuration);
mapper.Configure();
}
There is also this question that has lots of examples (some do not work in dotnet core anymore): Getting all types that implement an interface
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IPersonals, Personals>();
}
When I attempt to add my first migration to the project I get the following error:
No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext
What I am trying to achieve is to have my code that takes care of the DB in a .NET Core 3.0 class library separated from my web project.
So basically my file system looks like this:
Solution
-MyProject.Data
-MyProject.Domain
-MyProject.Web
To achieve this I have found a way to move my DB class from the web project using the IDesignTimeDbContextFactory and set it up in my data project.
But when I try to add migrations it fails.
Most of the answers suggest to remove the parameterless constructor from my DbContext but that is not working for me since I never had it there in the first place.
Here is my code, help is much appreciated since I found no resources for such a setup.
public class ApiDbContext : DbContext
{
public ApiDbContext(DbContextOptions<ApiDbContext> options) : base()
{
}
public DbSet<AplicationUser> AplicationUsers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var relationsship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationsship.DeleteBehavior = DeleteBehavior.Restrict;
}
ConfigureModelBuilderForUser(modelBuilder);
}
private void ConfigureModelBuilderForUser(ModelBuilder modelBuilder)
{
}
}
public class TemporaryDbContextFactory : IDesignTimeDbContextFactory<ApiDbContext>
{
public ApiDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApiDbContext>();
builder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=xxx;Trusted_Connection=True;MultipleActiveResultSets=true;User=xxx;Password=xxx",
optionsBulder => optionsBulder.MigrationsAssembly(typeof(ApiDbContext).GetTypeInfo().Assembly.GetName().Name));
return new ApiDbContext(builder.Options);
}
}
These two classes are in my Data project, and in my Web project I have a Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFrameworkSqlServer()
.AddDbContext<ApiDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("ApiDbContext"),
options => options.MigrationsAssembly("MyProject.Data")));
services.AddIdentity<AplicationUser, IdentityRole>();
services.AddControllersWithViews();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
I am using Automapper 6.2.2 and I'm trying to set it up in a Web App. I am trying to use the static Automapper.Initialize() method placed directly in my Global.asax file.
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.AllowNullCollections = true;
cfg.CreateMap<LoadArea, LoadAreaWithoutPlannedSlotDto>();
cfg.CreateMap<LoadArea, LoadAreaDto>();
cfg.CreateMap<LoadAreaForCreationDto, LoadArea>().Ignore(d => d.Slots);
cfg.CreateMap<LoadArea, LoadAreaForUpdateDto>();
cfg.CreateMap<LoadAreaForUpdateDto, LoadArea>().ForMember(m => m.Code, i => i.UseDestinationValue());
cfg.CreateMap<PlannedSlot, PlannedSlotDto>();
cfg.CreateMap<PlannedSlotForCreationDto, PlannedSlot>().Ignore(d => d.Area);
cfg.CreateMap<PlannedSlotForUpdateDto, PlannedSlot>();
cfg.CreateMap<UserToReturnDto, User>();
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>();
});
AreaRegistration.RegisterAllAreas();
UnityConfig.RegisterComponents();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
The strange issue is that while this code runs at startup, the mappings are created but none of them are actually configured.
So if I try to Ignore a property in the Mapper.Initialize(...) method, it doesn't work and I get an error when the unmapped property is run into when mapping occurs.
I tried using:
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>().ForMember(d => d.LoadArea, opt => opt.Ignore());
Also tried:
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>(MemberList.None);
And a few other combinations, including an extension method that would ignore all unmapped members:
public static IMappingExpression<TSource, TDestination> Ignore<TSource, TDestination>(this IMappingExpression<TSource, TDestination> map,
Expression<Func<TDestination, object>> selector)
{
map.ForMember(selector, config => config.Ignore());
return map;
}
But what does work is if I try to Ignore the property Inline in my controller as follows:
[HttpPost]
[Route("{loadAreaId}/details")]
public IHttpActionResult AddLoadAreaSlotDetails([FromUri] string loadAreaId, [FromBody] LoadAreaSlotDetailForAddDto loadAreaSlotDetails)
{
var loadAreaSlotDetailEntity = Mapper.Map<LoadAreaSlotDetailForAddDto, LoadAreaSlotDetail>(loadAreaSlotDetails, opt => opt.ConfigureMap().ForMember(d => d.LoadArea, o => o.Ignore()));
_repo.AddLoadAreaSlotDetail(loadAreaSlotDetailEntity);
return Ok();
}
This proves to me that the Ignore works but at the same time I assume that I'm Initializing and configuring my mappings wrongly but I don't know why because many other examples are Initializing in the same way using the static API. I'm doing the same in a .NET Core project (in the ConfigureServices method) and mappings work, it also ignores unmapped properties by default.
Why does this happen ?
Have you tried using AutoMapper Profiles?
AutoMapper Configuration
I was then able to configure this in the Startup.cs of my WebApi application. I was using SimpleInjector as my Container:
var profiles =
Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => typeof(Profile).IsAssignableFrom(t))
.ToList();
Mapper.Initialize(
mp =>
{
var mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfiles(profiles));
var mapper = mapperConfiguration.CreateMapper();
container.Register(() => mapper, Lifestyle.Scoped);
});
You then need to define one or more profiles depending on how you want to split out your auto mapper config.
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<UserDetails, UserTransferObject>();
CreateMap<UserAndAccountDetails, UserAndAccountTransferObject>();
CreateMap<User, UserAndAccountTransferObject>()
.ForMember(
dest => dest.DifferentPropertyName,
orig => orig.MapFrom(src => src.OriginalPropertyName));
}
}
I'm using dependency injection to register my DbContext in the controller of a ASP.NET MVC Core Application like this:
public void ConfigureServices(IServiceCollection services) {
return new MyContext(connectionString); }
services.AddScoped<IMyContext, MyContext>((serviceProdiver) => {
return new MyContext(Configuration["ConnectionStrings:MyContext"]);
});
services.AddMvc();
}
This works well. But now I want to use Migrations like Add-Migration Initial -Context MyContext which requires a parameterless constructor. But this would destroy the DI pattern because I would need to fallback to singleton pattern from classic ASP.NET MVC like the following:
public class MyContext:MySqlDbContext, IMyContext {
public MyContext() : base(Startup.Configuration["ConnectionStrings:MyContext"] {
}
}
I like to avoid this to consequently use DI in my new ASP.NET Core project. Is this possible using database-migrations or isn't the migration-tool updated yet for DI so that there is no alternative to use the old singleton pattern here?
Wow, you did very big mistake.
You need not to add some your configuration to MyContext, because it's
autogenerated file.
What have you to do ?
You have to add some your configuration file for example RegisterServices or it will be extension.
For example
public static class ServiceCollectionExtensions
{
public static IServiceCollection RegisterServices(
this IServiceCollection services)
{
services.AddTransient<ICountryService, CountryService>();
// and a lot more Services
return services;
}
}
And after that you have to Register this your configuration file in ConfigureServices, for example
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.RegisterServices();
}
Finally, you can use Services or Repositories
public class HomeController : Controller
{
private readonly ICountryService _countryService;
public HomeController(ICountryService countryService)
{
_countryService = countryService;
}
// …
}
Or, at the moment new in ASP.NET Core MVC is, that we can also inject this service into a MVC view. The following line defines the injection in a Razor view:
#inject DiViews.Services.ICountryService CountryService;
The first part after the #inject directive defines the interface. The second part is the name of the variable which holds our instance.
To inject a service globally into all Views, add this line to the _ViewImports.cshtml. In a complete new ASP.NET Core project, there is already a global injection defined for ApplicationInsights:
#inject Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration TelemetryConfiguration
We are now able to use the instance in our view:
#if (countryService.All().Any())
{
<ul>
#foreach (var country in CountryService.All().OrderBy(x => x.Name))
{
<p>#country.Name (#country.Code)</p>
}
</ul>
}
We can also use this service to fill select fields with the list of countries:
#Html.DropDownList("Coutries", CountryService.All()
.OrderBy(x => x.Name)
.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.Code
}))
I hope, it was helpful for you