How to inject IConfiguration .net settings into Profile class? - c#

So I'm my appsettings I have some string array filters that I need to use within mapping.
How can I pass them to MappingMethod below? (sample below does not work as MyProfile can't have parameters)
public class MyProfile: Profile
{
public MyProfile(IConfiguration configuration)
{
CreateMap<Source, Destination>()
.ForMember(dest => dest.X,
opt => opt.MapFrom(src => MappingMethod(src.NestedSource, configuration.GetSection("settingsArray")))
}
}

Related

Mapster not mapping

I'm using Mapster with DI and I'm trying to map objects that I receive from WS. I was following this guide https://github.com/MapsterMapper/Mapster/wiki/Dependency-Injection#mapping
I register TypeAdapterConfig, and ServiceMapper
var config = new TypeAdapterConfig();
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();
Blacklist class contains collection of Cards but webservice returns array of long, that I remap to object.
public class BlacklistMapper : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
config.NewConfig<long, Internal.BlacklistCard>()
.Map(dest => dest.Cuid, source => source);
config.NewConfig<SzWebService.BlackList, Internal.Blacklist>()
.Map(dest => dest.Id, source => source.iBlacklistId)
.Map(dest => dest.Crc, source => source.iBlackListCRC)
.Map(dest => dest.Cards, source => source.lCuid);
}
}
Inject mapper in the constuctor
private readonly IMapper _mapper;
public Service(IMapper mapper)
{
_logger = logger;
}
And finally call it like so
_mapper.Map<Blacklist>(response.mBlackListData)
The result is always object with default values
Step 1 - Create the configuration via implementing IRegister
public class BlacklistMapper : IRegister
{
void Register(TypeAdapterConfig config)
{
config.NewConfig<SzWebService.BlackList, Internal.Blacklist>()
.Map(...)
.Map(...);
}
}
Step 2 - Register the configuration
You can either register the configuration explicitly:
var config = new TypeAdapterConfig();
// Explicitly apply a specific configuration
config.Apply(new BlackListMapper());
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();
or let Mapster scan your assemblies for IRegister implementations:
// Scan & apply IRegisters automatically.
config.Scan(Assembly.GetExecutingAssembly());

EF Core - Return mapped Many-to-Many relationship from OData using Automapper

Info
The app type is a Hosted Blazor web-assembly. And below are the versions of the nuget packages I am using. There is an error that occurs when trying to expand a navigation property that is a many-to-many relationship. The classes are mapped to DTO classes that flattens the middle relationship class.
.Net core Version="3.1"
AutoMapper Version="10.0.0"
AutoMapper.AspNetCore.OData.EFCore Version="2.0.1"
AutoMapper.Extensions.ExpressionMapping Version="4.0.1"
AutoMapper.Extensions.Microsoft.DependencyInjection Version="8.0.1"
Microsoft.AspNetCore.Components.WebAssembly.Server Version="3.2.1"
Microsoft.AspNetCore.OData Version="7.5.0"
To run this repo, you will need the free version of SQL Server or better
Set the EfCoreAutomapperOdata.Server project as the startup project and navigate to the Courses page (https://localhost:5001/courses) and click on either course. This will throw the following error:
System.InvalidOperationException: No generic method 'Include' on type 'Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic. at System.Linq.Expressions.Expression.FindMethod(Type type, String methodName, Type[] typeArgs, Expression[] args, BindingFlags flags)...
Models
See here - Entity Models and here - Dto Models for class definition
Automapper Config
public class AutomapperConfig : Profile
{
public AutomapperConfig()
{
CreateMap<Instructor, InstructorDto>();
CreateMap<InstructorDto, Instructor>();
CreateMap<Course, CourseDto>()
.ForMember(dto => dto.Students, opt => {
opt.MapFrom(_ => _.Students.Select(y => y.Student));
});
CreateMap<CourseDto, Course>()
.ForMember(ent => ent.Students, ex => ex
.MapFrom(x => x.Students.Select(y => new CourseStudent {
CourseId = x.Id,
StudentId = y.Id
})));
CreateMap<Student, StudentDto>()
.ForMember(dto => dto.Courses, opt => {
opt.MapFrom(x => x.Courses.Select(y => y.Course));
})
.ForMember(dto => dto.Friends, opt => {
opt.MapFrom(x => x.Friends.Select(y => y.Friend));
});
CreateMap<StudentDto, Student>()
.ForMember(ent => ent.Courses, ex => ex
.MapFrom(x => x.Courses.Select(y => new CourseStudent
{
StudentId = x.Id,
CourseId = y.Id
})));
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// ------ Some code removed for brevity ------
services.AddOData();
services.AddAutoMapper(cfg => { cfg.AddExpressionMapping(); },typeof(AutomapperConfig));
// ------ Some code removed for brevity ------
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ------ Some code removed for brevity ------
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.EnableDependencyInjection();
endpoints.Select().Filter().OrderBy().Count().Expand().MaxTop(1000);
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
endpoints.MapFallbackToFile("index.html");
});
}
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<CourseDto>("Courses");
builder.EntitySet<InstructorDto>("Instructors");
builder.EntitySet<StudentDto>("Students");
return builder.GetEdmModel();
}
}
Course Controller
public class CourseController : ODataController
{
protected readonly BlazorContext _context;
protected readonly IMapper _mapper;
public CourseController(BlazorContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[HttpGet]
[ODataRoute("Courses")]
public async Task<IActionResult> Get(ODataQueryOptions<CourseDto> options)
{
return Ok(await _context.Course.GetAsync(_mapper, options));
}
[HttpGet]
[ODataRoute("Courses({id})")]
public async Task<IActionResult> Get([FromODataUri] int id, ODataQueryOptions<CourseDto> options)
{
return Ok((await _context.Course.GetAsync(_mapper, options)).Where(s => s.Id == id).ToList());
}
}
Sample odata api query that fails
/odata/Courses?$expand=Students
Repro
I have built demo Blazor WASM app for this issue to reproduce
Repository
General Setup
To make expansions work, you need to allow for the $expand query option to be used. Configure it explicitly like so:
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<EstimateDto>(nameof(MyContext.Estimates))
.EntityType
.Expand(); // <-- allow expansion
builder.EntitySet<TypeDto>(nameof(MyContext.Types))
.EntityType
.Expand(); // <-- allow expansion
builder.EntitySet<SubTypeDto>(nameof(MyContext.SubTypes));
return builder.GetEdmModel();
}
You will also need to update your AutoMapper map, to allow the queries to be successfully mapped to a DTOs:
public class AutoMapperConfig : Profile
{
public AutoMapperConfig()
{
CreateMap<Estimate, EstimateDto>()
.ForMember(
dto => dto.Types,
opt => opt.MapFrom(x => x.EstimateTypes.Select(y => y.Type)));
// The following mapping is needed for expansion to work:
CreateMap<EstimateTypeRel, TypeDto>()
.ForMember(
dto => dto.SubTypes,
opt => opt.MapFrom(x => x.Type));
CreateMap<Type, TypeDto>()
.ForMember(
dto => dto.SubTypes,
opt => opt.MapFrom(x => x.SubTypes.Select(y => y.SubType)));
CreateMap<SubTypeRel, SubTypeDto>();
}
}
With that configuration being setup, there are at least two possible solutions to this issue, depending on your requirements:
A) Only expand Types
If you only want to expand Types, you will need to change your AutoMapper mappings by adding a .Where(z => z != null) clause, because as the exception tells you, null values are not allowed in collections, but OData includes them for the non-expanded SubType entities:
public class AutoMapperConfig : Profile
{
public AutoMapperConfig()
{
CreateMap<Estimate, EstimateDto>()
.ForMember(
dto => dto.Types,
opt => opt.MapFrom(
x => x.EstimateTypes.Select(y => y.Type)
.Where(z => z != null))); // <-- filter out null values
CreateMap<EstimateTypeRel, TypeDto>()
.ForMember(
dto => dto.SubTypes,
opt => opt.MapFrom(x => x.Type));
CreateMap<Type, TypeDto>()
.ForMember(
dto => dto.SubTypes,
opt => opt.MapFrom(
x => x.SubTypes.Select(y => y.SubType)
.Where(z => z != null))); // <-- filter out null values
CreateMap<SubTypeRel, SubTypeDto>();
}
}
Then you can use the following query:
https://localhost:5001/odata/Estimates(1)?$expand=Types
B) Also expand SubTypes
The alternative is to expand the SubTypes property as well, so the collection can be properly filled. To expand DTO mapped properties over multiple levels, use the $expand query option in your query string like so:
https://localhost:5001/odata/Estimates(1)?$expand=Types($expand=SubTypes)

Automapping issue

I am new to AutoMapper ✌ and struggling with the mapping of an array with values to a Entity Framework model.
It should be very simple but I get the exception for several hours right now.. (Missing type map configuration or unsupported mapping)
I just created a class automapper configuration class:
public static class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.CreateMap<Response<Car>, Car>()
.ForMember(dst => dst.Brand, opt => opt.MapFrom(src => src.Content.Values.Where(p => p.Code == "brand").FirstOrDefault().Content))
.ForMember(dst => dst.Color, opt => opt.MapFrom(src => src.Content.Values.Where(p => p.Code == "color").FirstOrDefault().Content))
.ForMember(dst => dst.BuildYear, opt => opt.MapFrom(src => src.Content.Values.Where(p => p.Code == "buildyear").FirstOrDefault().Content))
.ForMember(dst => dst.Price, opt => opt.MapFrom(src => src.Content.Values.Where(p => p.Code == "price").FirstOrDefault().Content));
}
}
}
And I call this in my startup class..
AutoMapperConfiguration.Configure();
And this where the mapping should happen. (In a hangfire job)
Mapper.Map<Car>(response.Content.Values));
Is seems that the mapping configuration is unsupported, are there any alternatives to fix this issue?

Automapper not working with .MapFrom on ternary and calculated value with Net Core 2.2

I ma using;
AutoMapper.Extensions.Microsoft.DependencyInjection 6.0.0
in a web api project trunning net core 2.2
when mapping my DTO object I use Automapper to map a few fields;
public class AutoMapperProfile : AutoMapper.Profile
{
public AutoMapperProfile()
{
CreateMap<ReviewPostInputModel, Review>()
.ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.MapFrom(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
.ForMember(x => x.ReceiveUpdates, opt => opt.MapFrom(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
.ForMember(x => x.AverageScore, opt => opt.MapFrom(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
// ...
}
}
Where;
using System;
using System.Collections.Generic;
using System.Text;
public class Review
{
// ...
public decimal Reliability { get; set; }
public decimal Tidiness { get; set; }
public decimal Courtsey { get; set; }
public decimal Workmanship { get; set; }
public decimal AverageScore { get; set; }
public DateTime? ReceiveUpdates { get; set; }
public DateTime? ReceiveThirdPartyUpdates { get; set; }
}
However, when I try to map using;
var review = _mapper.Map<Review>(model);
All standard members are mapped bar my ForMember listed above, where the DateTimes are set to a new instance of DateTime and Averagescore is set to 0.
For completness I DI the mapper into my controller as follows;
private readonly IMapper _mapper;
public ReviewController( IMapper mapper)
{
_mapper = mapper;
}
I configure Automapper in my StartUp.cs as follows;
services.AddAutoMapper();
I have also tried adding a test to the controller to confirm that the values from the input are not the issue (completed after the map and can confirm that this value is correctly updated);
review.AverageScore = (decimal)Math.Round((model.Courtsey + model.Reliability + model.Tidiness + model.Workmanship) / 4, 2);
Does anyone have any ideas why this is occurring?
You need to use "ResolveUsing" and not "MapFrom"
public class AutoMapperProfile : AutoMapper.Profile
{
public AutoMapperProfile()
{
CreateMap<ReviewPostInputModel, Review>()
.ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.ResolveUsing(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
.ForMember(x => x.ReceiveUpdates, opt => opt.ResolveUsing(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
.ForMember(x => x.AverageScore, opt => opt.ResolveUsing(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
// ...
}
}
You can look on this answer:
AutoMapper: What is the difference between MapFrom and ResolveUsing?
We had a problem in the past when adding auto mapper from "ConfigureServices" and may be it is the same for you.
You can try to put this:
AutoMapperConfiguration.Init();
in Startup function
and add this class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
namespace yournamespace.ViewModels.Mappings
{
public static class AutoMapperConfiguration
{
public static void Init()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ReviewPostInputModel, Review>()
.ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.MapFrom(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
.ForMember(x => x.ReceiveUpdates, opt => opt.MapFrom(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
.ForMember(x => x.AverageScore, opt => opt.MapFrom(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
});
}
}
}
After some investigation, I think I have reproduced your problem. I created a basic ASP.NET Core 2.2 website, installed AutoMapper.Extensions.Microsoft.DependencyInjection 6.0.0, created a Review class that matches yours and a best guess of what ReviewPostInputModel class looks like based on your mapping definition. I then added your mapping profile class AutoMapperProfile into the project, and configured start up like so:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAutoMapper();
services.AddMvc...
}
I then "hacked" the default generated HomeController like so to test the mapping:
public class HomeController : Controller
{
private IMapper _mapper;
public HomeController(IMapper mapper)
{
_mapper = mapper;
}
public IActionResult Index()
{
var input = new ReviewPostInputModel();
input.ReceiveThirdPartyUpdates = true;
input.Tidiness = 3;
input.Reliability = 2;
input.NotDefinedOnProfile = "sss";
var output = _mapper.Map<Review>(input);
// Lazy test to avoid changing model.
throw new Exception($"{output.ReceiveThirdPartyUpdates} - {output.AverageScore} - {output.NotDefinedOnProfile}");
return View();
}
...
Now this worked for me, as in the Exception message I received was 11/04/2019 2:56:31 PM - 1.25 - sss.
I then created another assembly and moved the AutoMapperProfile class into it. I then re-run the test, but got the following error:
AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the >source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of ?the constructor parameters
AutoMapper created this type map for you, but your types cannot be mapped using the >current configuration.
ReviewPostInputModel -> Review (Destination member list)
ReviewPostInputModel -> Review (Destination member list)
Unmapped properties:
AverageScore
ReceiveUpdates
NotDefinedOnProfile
This makes sense, since the services.AddAutoMapper(); method only searches the currently assembly for profiles.
So I then changed to configuration to: services.AddAutoMapper(cfg => cfg.ValidateInlineMaps = false); to turn off the error, and re-run the test.
New Output:
1/01/0001 12:00:00 AM - 0 - sss
So this leads me to believe that AutoMapper can't find your Profile class, in which case, you can manually configure it using:
services.AddAutoMapper(cfg =>
{
cfg.AddProfile<AutoMapperProfile>();
});
or manually define the assemblies to search by using one of the other overloads e.g.:
services.AddAutoMapper(param System.Reflection.Assembly[] assemblies);
If this is not your problem, no matter because my time spent reproducing this question has cured my insomnia.

How to combine mapping expressions during configuration for use in a Container

Given the following code:
public static IMapper ConfigureMapper()
{
var config = new MapperConfiguration(cfg => {
cfg.ShouldMapField = fi => false;
cfg.ShouldMapProperty = pi => pi.GetMethod != null && (pi.GetMethod.IsPublic || pi.GetMethod.IsVirtual);
cfg.CreateMap<ServiceModel.Types.NonRiskRequirement, RequiredSignature>()
.ForMember(dest => dest.ApplicantFlag, opt => opt.MapFrom(src => src.RequiredSignatureApplicantFlag))
.ForMember(dest => dest.InsuredFlag, opt => opt.MapFrom(src => src.RequiredSignatureInsuredFlag));
cfg.CreateMap<ServiceModel.Types.NonRiskRequirement, NonRiskWorkItem>()
.ForMember(dest => dest.RequiredSignature, opt => opt.MapFrom(
src => Mapper.Map<ServiceModel.Types.NonRiskRequirement, RequiredSignature>(src)))
.ForMember(dest => dest.WorkType, opt => opt.MapFrom(src => src.WorkType));
});
config.AssertConfigurationIsValid();
return config.CreateMapper();
}
What is the proper syntax to get rid of the static "Mapper.Map"?, I'm using AutoMapper 5.1.1 with Autofac 4.1.1 and getting this exception back.
System.InvalidOperationException: Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
Thank you,
Stephen
Don't use MapFrom, use ResolveUsing. It gives you a ResolutionContext object that includes a Mapper property that you use to map, not this static way, which is not recommended, and also now broken.

Categories

Resources