Autofac - ForAllOtherMembers - Mapper not used - automapper

I have implemented the CaseInsensitiveFromStringDictionaryMapper as proposed here https://github.com/AutoMapper/AutoMapper/issues/3243
I also have registered this mapper into autofac with the following code
protected void ConfigureAutomapper(ContainerBuilder builder)
{
var config = new MapperConfiguration(cfg =>
{
foreach (var assembly in GetAssemblies(CustomAssembliesPrefix))
{
cfg.AddMaps(assembly);
}
cfg.Mappers.Insert(0, new CaseInsensitiveFromStringDictionaryMapper());
});
try
{
config.AssertConfigurationIsValid();
}
catch (AutoMapperConfigurationException ex)
{
var message = new StringBuilder();
message.AppendLine("Automapper configuration error");
Exception exception = ex;
while (exception != null)
{
message.AppendLine($"Message: {ex.Message}");
message.AppendLine($"Stack trace: ");
message.AppendLine($"{ex.StackTrace}");
message.AppendLine($"");
exception = exception.InnerException;
}
EventLogProvider.LogEvent(EventType.ERROR, nameof(WebContainer), "Automapper configuration error", message.ToString());
throw;
}
builder.Register(c => new Mapper(config))
.As<IMapper>()
.SingleInstance();
}
if I just do that, I am able to execute the following code and it maps all the properties of my target object perfectly:
mapper.Map<ReferenceSearchResultDocument>(documentSearchResult.Document)
But I need to some specific stuffs on some members so I have created a Profile and a mapping for those members but I would like to have the default behavior (so watching case insensitively in my dictionary) for my other members.
I have tested the following code:
.ForAllOtherMembers(opt => opt.MapFrom(src => src[opt.DestinationMember.Name]))
but of course it isn't the same as letting the mappers doing they jobs.
How could I fix it?
Regards,
Benjamin

Related

Class exception when trying to move a email using the spring integration message payload

Hi I have configured the default filter as below and if some emails have a certain subject or from address I try to move them to specific folders using java mail api as below:
Filter implementation:
#Bean(name = ImapAdaptersUtil.DEFAULT_FILTER_BEAN_NAME)
#Scope(WebApplicationContext.SCOPE_APPLICATION)
public MessageSelector defaultFilter() {
return message -> {
if (message.getPayload() instanceof MimeMessage) {
try {
String from = Optional.ofNullable(((InternetAddress) ((MimeMessage) message.getPayload()).getFrom()[0]).getAddress()).orElse(GeneralConst.EMPTY_STRING);
String subject = Optional.ofNullable(((MimeMessage) message.getPayload()).getSubject()).orElse(GeneralConst.EMPTY_STRING);
if (!from.matches(DELIVERY_ERROR_FROM)
&& !from.matches(SPAM_FROM)
&& !subject.matches(DELIVERY_ERROR_SUBJECT)
&& !subject.matches(OUT_OF_OFFICE)
&& !subject.matches(SPAM_SUBJECT)) {
return true;
}
} catch (MessagingException me) {
throw new ApplicationBusinessException(ApplicationBusinessException.ApplicationBusinessExceptionType.FUNCTIONAL_VIOLATION,
"Could not filter incoming email: " + me.getMessage());
}
}
try {
this.moveMessage(((MimeMessage) message.getPayload()));
} catch (MessagingException me) {
throw new ApplicationBusinessException(ApplicationBusinessException.ApplicationBusinessExceptionType.FUNCTIONAL_VIOLATION,
"Could not move incoming email: " + me.getMessage());
}
return false;
};
}
Move to folder implementation:
private void moveMessage(MimeMessage message) throws MessagingException {
Folder folder = message.getFolder();
Store store = folder.getStore();
Folder[] folders = store.getDefaultFolder().list("*");
for (Folder folder1 : folders) {
LOGGER.info("folder name {}", folder1.getName());
}
Folder deliveryErrorFolder = store.getFolder("Delivery error");
if (!deliveryErrorFolder.exists()) {
if (deliveryErrorFolder.create(Folder.HOLDS_MESSAGES)) {
deliveryErrorFolder.setSubscribed(true);
move(message, folder, deliveryErrorFolder);
LOGGER.info("Delivery error created");
}
} else {
move(message, folder, deliveryErrorFolder);
}
}
private void move(MimeMessage message, Folder folder, Folder spamFolder) throws MessagingException {
List<javax.mail.Message> tempList = new ArrayList<>();
tempList.add(message);
javax.mail.Message[] tempMessageArray = tempList.toArray(new javax.mail.Message[0]);
folder.copyMessages(tempMessageArray, spamFolder);
LOGGER.info("message moved");
}
ImapMailReceiver configured as an Integration flow :
public static IntegrationFlow getImapAdapterIntegrationFlow(String imapsUrl, MessageSelector filter, QueueChannelSpec channelSpec) {
return IntegrationFlows
.from(Mail.imapInboundAdapter(imapsUrl)
.userFlag("testSIUserFlag")
.simpleContent(false)
.autoCloseFolder(false)
.shouldMarkMessagesAsRead(true)
.javaMailProperties(getPropertiesBuilderConsumer()),
e -> e.autoStartup(true)
.poller(p -> p.fixedDelay(1000)))
.filter(filter)
.channel(channelSpec)
.get();
}
I get this exception :
Caused by: java.lang.ClassCastException: class org.springframework.integration.mail.AbstractMailReceiver$IntegrationMimeMessage cannot be cast to class com.sun.mail.imap.IMAPMessage (org.springframework.integration.mail.AbstractMailReceiver$IntegrationMimeMessage and com.sun.mail.imap.IMAPMessage are in unnamed module of loader 'app')
at com.sun.mail.imap.Utility.toMessageSet(Utility.java:85)
Yeah... What you are looking for is available starting with version 5.4: https://docs.spring.io/spring-integration/docs/current/reference/html/mail.html#mail-inbound
Starting with version 5.4, it is possible now to return a MimeMessage as is without any conversion or eager content loading. This functionality is enabled with this combination of options: no headerMapper provided, the simpleContent property is false and the autoCloseFolder property is false.
So, all good in your config - only what you need to to upgrade your project to the latest Spring Integration. Directly with the 5.4.5 or via respective latest Spring Boot.

Unit testing that the swagger doc is correct without starting a server

I'd like to test that the swagger document is correct for my application (mainly, because I've added a strategy to generate custom OperationIds and I want to ensure they are correctly unique)
However, the only solutions I found are all using a "real" server (cf https://stackoverflow.com/a/52521454/1545567), which is not an option for me since I do not have the database, message bus, etc... when I launch the unit tests in CI...
At the moment, I have the following but it always generate 0 paths and 0 models ...
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using SampleCheckIn;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Linq;
using Xunit;
using SampleCheckIn.Def;
using Service.Utils;
using Swashbuckle.AspNetCore.Swagger;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
namespace D4Interop.Tests
{
public class TmpTest
{
[Fact]
public void Tmp()
{
var controllers = typeof(Startup).Assembly.GetTypes().Where(x => IsController(x)).ToList();
controllers.Any().Should().BeTrue();
var services = new ServiceCollection();
controllers.ForEach(c => services.AddScoped(c));
services.AddLogging(logging => logging.AddConsole());
services.AddControllers(); //here, I've also tried AddMvcCore and other ASP methods...
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("api", new OpenApiInfo { Title = Constants.SERVICE_NAME, Version = "_", Description = Constants.SERVICE_DESC });
//c.OperationFilter<SwaggerUniqueOperationId>(); //this is my filter that ensures the operationId is unique
c.CustomOperationIds(apiDesc =>
{
return apiDesc.TryGetMethodInfo(out var methodInfo) ? methodInfo.Name : null;
});
});
services.AddSingleton<IWebHostEnvironment>(new FakeWebHostEnvironment());
var serviceProvider = services.BuildServiceProvider();
var swaggerProvider = serviceProvider.GetRequiredService<ISwaggerProvider>();
var swagger = swaggerProvider.GetSwagger("api");
swagger.Should().NotBeNull();
swagger.Paths.Any().Should().BeTrue();
}
private bool IsController(Type x)
{
return typeof(Microsoft.AspNetCore.Mvc.ControllerBase).IsAssignableFrom(x);
}
}
internal class FakeWebHostEnvironment : IWebHostEnvironment
{
public FakeWebHostEnvironment()
{
}
public IFileProvider WebRootFileProvider { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public string WebRootPath { get => "/root"; set => throw new NotImplementedException(); }
public string EnvironmentName { get => "dev"; set => throw new NotImplementedException(); }
public string ApplicationName { get => "app"; set => throw new NotImplementedException(); }
public string ContentRootPath { get => "/"; set => throw new NotImplementedException(); }
public IFileProvider ContentRootFileProvider { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}
}
Ok, I've finally found that I just need to mix the linked answer with my code :
[Fact]
public async Task TestSwagger()
{
var server = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(options => { options.UseStartup<Startup>(); })
.Build();
var swagger = server.Services
.GetRequiredService<ISwaggerProvider>()
.GetSwagger("xxx"); //xxx should be the name of your API
swagger.Should().NotBeNull();
swagger.Paths.Any().Should().BeTrue();
swagger.Components.Schemas.Should().NotBeNull();
}

How to filter users in azure b2c based on custom user attribute?

I have created a custom user attribute customerId in Azure B2C user attributes to distinguish users of a particular customer. I can create users using Graph API and .net B2C sdk and set the customerId using this git hub sample
The problem I have is I can't filter customers by customerId. My code looks like below
public static async Task<List<User>> GetAllB2CUsersByCustomerId(GraphServiceClient graphClient, int customerId)
{
try
{
// Get users by customerId
var result = await graphClient.Users
.Request()
.Filter($"additionalData/any(c:c/key eq 'extension_b2cApplicationIdWithoutDashes_customerId' and c/value eq '{customerId}')")
.Select(e => new
{
e.DisplayName,
e.Id,
e.Identities
})
.GetAsync();
if (result != null)
{
return result.ToList();
}
}
catch (Exception ex)
{
// catch exception
}
return null;
}
and I get the following exception when the code runs
Code: BadRequest
Message: Filter not supported.
Inner error:
AdditionalData:
request-id: 21d0c9d3-7d6a-4c97-9066-c99f678aec54
date: 2020-06-10T15:59:37
ClientRequestId: 21d0c9d3-7d6a-4c97-9066-c99f678aec54
Just if it helps someone, the following syntax filters on the custom attribute
public static async Task<List<User>> GetAllB2CUsersByCustomerId(GraphServiceClient graphClient, int customerId)
{
try
{
// Get users by customerId
var result = await graphClient.Users
.Request()
.Filter($"extension_b2cApplicationIdWithoutDashes_customerId eq {customerId}")
.Select(e => new
{
e.DisplayName,
e.Id,
e.Identities
})
.GetAsync();
if (result != null)
{
return result.ToList();
}
}
catch (Exception ex)
{
// catch exception
}
return null;
}
When filtering on identities, you must supply both issuer and issuerAssignedId.Please refer this document
.Filter("identities/any(c:c/issuerAssignedId eq 'j.smith#yahoo.com' and c/issuer eq 'contoso.onmicrosoft.com')")

How to remove thread safe error in aspnet core middleware invoke method?

Getting exception "A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe."
Below is the code.
public Task Invoke(HttpContext context)
{
try
{
var userId =context.Session.GetString("UserId");
if (userId != null)
{
var user =_context.Users.Where(u => u.Id == userId).FirstOrDefault();
user.TimeStamp = DateTime.Now;
_context.SaveChanges();
}
else
{ }
}
catch(Exception ex)
{
_logger.LogError(ex.Message);
}
// Call the next delegate/middleware in the pipeline
return this._next(context);
}
I can't tell from your code but I'm guessing:
_context is your db context
you're injecting it via the ctor
As you've discovered, this probably won't work.
Instead, inject your db context via the Invoke method.
public Task Invoke(HttpContext context, YourDbContext dbContext)
{
try
{
var userId = context.Session.GetString("UserId");
if (userId != null)
{
var user = dbContext.Users.Where(u => u.Id == userId).FirstOrDefault();
...
}
}
catch(Exception ex){...}
}
This ensures you have a scoped instance of your db context, which should be threadsafe.

Changing GUID change program behaviour

I'm currently working on creating a namespace extension.
So I want to create an entry in Computer, and had explorer.exe calling my IShellFolder implementation.
I had it working for few minutes (I stupidly decided to clean up the code before commiting), so I'm somewhere near.
But I noticed something very strange: changing the GUID value of the Class change what I see in explorer.exe
I found that question which tells me I am not doing something bad
Here's the code:
AssemblyInfo.cs:
[assembly: ComVisible(false)]
[assembly: Guid("007C5100-4251-47BE-8141-D2AD3F496E6A")]
RootFolder.cs:
[ClassInterface(ClassInterfaceType.None)
[Guid("007C5101-4251-47BE-8141-D2AD3F496E6A"), ComVisible(true)]
public class RootFolder : IShellFolder, IShellFolder2, IPersistFolder, IPersistFolder2 {
private const String _mountPoint = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MyComputer\\NameSpace\\{0}";
private const String _mountName = "CLSID\\{0}";
#region Shell Extension Registration
[ComRegisterFunction()]
public static void Register(Type t)
{
Console.WriteLine("Registering {0}...", t.GUID);
try {
if (t.GUID == null)
throw new ArgumentException("CLSID must not be null");
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(String.Format(_mountPoint, t.GUID.ToString("B")))) {
key.SetValue(null, "RootFolder");
}
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey(String.Format(_mountName, t.GUID.ToString("B")), true)) {
key.SetValue(null, "RootFolder");
using (RegistryKey shFolder = key.CreateSubKey("ShellFolder")) {
shFolder.SetValue("Attributes", 0x78000040);
shFolder.SetValue("WantsFORPARSING", "");
}
}
using (RegistryKey key = Registry.LocalMachine.CreateSubKey(#"Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved")) {
key.SetValue(t.GUID.ToString("B"), "RootFolder");
}
IntPtr pidl = NativeMethod.SHGetKnownFolderIDList(KnownFolder.ComputerFolder.clsid, 0, IntPtr.Zero);
NativeMethod.SHChangeNotify(NativeMethod.FSNotification.UpdateDir, NativeMethod.ItemMeaning.IDList, pidl, IntPtr.Zero);
} catch (Exception ex) {
Logger.Write(Logger.Severity.Fatal, "Registration error: {0}", ex.Message);
throw; // Re-throw the exception
}
}
[ComUnregisterFunction()]
public static void Unregister(Type t)
{
try {
if (t.GUID == null)
throw new ArgumentException("CLSID must not be null");
Registry.CurrentUser.DeleteSubKey(String.Format(_mountPoint, t.GUID.ToString("B")), false);
using (RegistryKey k = Registry.ClassesRoot.OpenSubKey(String.Format(_mountName, t.GUID.ToString("B")), true)) {
if (k != null)
k.DeleteSubKey("ShellFolder");
}
using (RegistryKey key = Registry.LocalMachine.CreateSubKey(#"Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved")) {
if (key != null)
key.DeleteValue(t.GUID.ToString("B"), false);
}
IntPtr pidl = NativeMethod.SHGetKnownFolderIDList(KnownFolder.ComputerFolder.clsid, 0, IntPtr.Zero);
NativeMethod.SHChangeNotify(NativeMethod.FSNotification.UpdateDir, NativeMethod.ItemMeaning.IDList, pidl, IntPtr.Zero);
} catch (Exception ex) {
Logger.Write(Logger.Severity.Critical, "Registration error: {0}", ex.Message);
throw; // Re-throw the exception
}
}
#endregion
#region IShellFolder2 Inheritance
[All the methods looks like that, this is just a test]
public IEnumIDList EnumObjects(IntPtr hwndOwner, EnumObject flags)
{
Logger.Write("Tracing....");
throw new NotImplementedException();
}
#endregion
#region IPersistFolder2 Inheritance
[Ditto]
#endregion
}
I use the range 007C5{100...120}-4251-47BE-8141-D2AD3F496E6A for my GUIDs
When my extension has GUID 007C5101, it just shows "RootFolder" without any attributes.
When it has 007C5100 (same as assembly), or 007C5102, or 007C5103, it is shown as "System Folder" (even though it doesnt call my DLL).
I'm a little puzzled by this behaviour, how in the world can changing a GUID lead to this change ?
Note: I ran a search through the registry for 4251-47BE-8141-D2AD3F496E6A (the immutable part of my GUID), and couldn't find anything after unregistration.

Resources