I have an ever growing switch case statement I plan on adding 3 more case statements to. Given an int, compare it to an enum, and call IMapper.Map<TDestination>(sourceObject);
public WebhookActivity MakeMessage(WebhookActivityRequest request)
{
WebhookActivity command = _mapper.Map<ConsecutiveCaseCreated>(request);
var mappingId = 0;
for(int i = 0; i < mappings.Length; i++)
{
if(request.WorkflowCode == mappings[i].WorkflowCode)
{
mappingId = mappings[i].EventHandlerId;
}
}
switch(mappingId)
{
case (int)WorkflowEventHandler.CONSECUTIVE_CASE_CREATED:
{
command = _mapper.Map<ConsecutiveCaseCreated>(request);
break;
}
case (int)WorkflowEventHandler.CONSECUTIVE_CASE_MODIFIED:
{
command = _mapper.Map<ConsecutiveCaseModified>(request);
break;
}
case (int)WorkflowEventHandler.CONSECUTIVE_CASE_CANCELLED:
{
command = _mapper.Map<ConsecutiveCaseCancelled>(request);
break;
}
case (int)WorkflowEventHandler.INTERMITTENT_CASE_CREATED:
{
command = _mapper.Map<IntermittentCaseCreated>(request);
break;
}
case (int)WorkflowEventHandler.INTERMITTENT_CASE_TIME_ADJUSTED:
{
command = _mapper.Map<IntermittentCaseTimeAdjusted>(request);
break;
}
case (int)WorkflowEventHandler.INTERMITTENT_CASE_TIME_DELETED:
{
command = _mapper.Map<IntermittentCaseTimeDeleted>(request);
break;
}
case (int)WorkflowEventHandler.CASE_CHANGED_TYPE:
{
command = _mapper.Map<CaseChangedType>(request);
break;
}
}
return command;
}
I looked at other enum related questions and saw a common solution of using a dictionary. I tried that and couldn't quite figure out how to make the call to IMapper.Map() work out. Made the code a lot smaller though! Here's what the dictionary based solution I came up with looks like and what the method code became.
private static readonly Dictionary<int, Type> workflowEventHandlers = new Dictionary<int, Type>
{
{ (int)WorkflowEventHandler.CONSECUTIVE_CASE_CREATED, typeof(ConsecutiveCaseCreated)},
{ (int)WorkflowEventHandler.CONSECUTIVE_CASE_CANCELLED, typeof(ConsecutiveCaseCancelled) },
{ (int)WorkflowEventHandler.CONSECUTIVE_CASE_MODIFIED, typeof(ConsecutiveCaseModified) },
{ (int)WorkflowEventHandler.INTERMITTENT_CASE_CREATED, typeof(IntermittentCaseCreated) },
{ (int)WorkflowEventHandler.INTERMITTENT_CASE_TIME_ADJUSTED, typeof(IntermittentCaseTimeAdjusted) },
{ (int)WorkflowEventHandler.INTERMITTENT_CASE_TIME_DELETED, typeof(IntermittentCaseTimeDeleted) },
{ (int)WorkflowEventHandler.CASE_CHANGED_TYPE, typeof(CaseChangedType) }
};
public WebhookActivity MakeMessage(WebhookActivityRequest request)
{
WebhookActivity command = _mapper.Map<ConsecutiveCaseCreated>(request);
var mappingId = 0;
for(int i = 0; i < mappings.Length; i++)
{
if(request.WorkflowCode == mappings[i].WorkflowCode)
{
mappingId = mappings[i].EventHandlerId;
}
}
if(workflowEventHandlers.TryGetValue(mappingId, out Type mapDestinationType))
{
//command = _mapper.Map(request, typeof(WebhookActivityRequest), mapDestinationType);
//command = Map(request, mapDestinationType);
}
}
// doesn't really work
private WebhookActivityMessage Map<TSource, TDestination>(TSource source, TDestination destination) where TDestination: WebhookActivityMessage
{
return _mapper.Map<TSource, TDestination>(source);
}
The destination types all inherit from a base class called WebhookActivityMessage
which inherits from WebhookActivity
so I figured if I could say the return type of my Map() method was WebhookActivityMessage
then it could be happy.
3 Answers 3
You can use a switch expression (C# 8) instead of a switch statement to make the code a lot smaller:
public WebhookActivity MakeMessage(WebhookActivityRequest request)
{
int mappingId = 0;
for (int i = 0; i < mappings.Length; i++) {
if (request.WorkflowCode == mappings[i].WorkflowCode) {
mappingId = mappings[i].EventHandlerId;
}
}
return (WorkflowEventHandler)mappingId switch {
WorkflowEventHandler.CONSECUTIVE_CASE_CREATED => _mapper.Map<ConsecutiveCaseCreated>(request),
WorkflowEventHandler.CONSECUTIVE_CASE_MODIFIED => _mapper.Map<ConsecutiveCaseModified>(request),
WorkflowEventHandler.CONSECUTIVE_CASE_CANCELLED => _mapper.Map<ConsecutiveCaseCancelled>(request),
WorkflowEventHandler.INTERMITTENT_CASE_CREATED => _mapper.Map<IntermittentCaseCreated>(request),
WorkflowEventHandler.INTERMITTENT_CASE_TIME_ADJUSTED => _mapper.Map<IntermittentCaseTimeAdjusted>(request),
WorkflowEventHandler.INTERMITTENT_CASE_TIME_DELETED => _mapper.Map<IntermittentCaseTimeDeleted>(request),
WorkflowEventHandler.CASE_CHANGED_TYPE => _mapper.Map<CaseChangedType>(request),
_ => _mapper.Map<ConsecutiveCaseCreated>(request)
};
}
But note that only the last mappingId
in your for-loop will be used. Did you want to search for the first occurrence instead?
int mappingId = mappings
.FirstOrDefault(m => request.WorkflowCode == m.WorkflowCode)?.EventHandlerId ?? 0;
As for your solution with the dictionary, you could declare a
Dictionary<WorkflowEventHandler,
Func<IMapper, WebhookActivityRequest, WebhookActivity>>
instead. I.e., you would add it lambda expressions doing the work.
private static readonly Dictionary<WorkflowEventHandler, Func<IMapper, WebhookActivityRequest, WebhookActivity>> workflowEventHandlers = new(){
{ WorkflowEventHandler.CONSECUTIVE_CASE_CREATED, (m, r) => m.Map<ConsecutiveCaseCreated>(r) },
{ WorkflowEventHandler.CONSECUTIVE_CASE_CANCELLED, (m, r) => m.Map<ConsecutiveCaseCancelled>(r) },
{ WorkflowEventHandler.CONSECUTIVE_CASE_MODIFIED, (m, r) => m.Map<ConsecutiveCaseModified>(r) },
{ WorkflowEventHandler.INTERMITTENT_CASE_CREATED, (m, r) => m.Map<IntermittentCaseCreated>(r) },
{ WorkflowEventHandler.INTERMITTENT_CASE_TIME_ADJUSTED,(m, r) => m.Map<IntermittentCaseTimeAdjusted>(r) },
{ WorkflowEventHandler.INTERMITTENT_CASE_TIME_DELETED, (m, r) => m.Map<IntermittentCaseTimeDeleted>(r) },
{ WorkflowEventHandler.CASE_CHANGED_TYPE, (m, r) => m.Map<CaseChangedType>(r) }
};
Then you can use it like this
if (workflowEventHandlers.TryGetValue((WorkflowEventHandler)mappingId, out var mapCreator)) {
command = mapCreator(_mapper, request);
} else {
command = _mapper.Map<ConsecutiveCaseCreated>(request);
}
-
\$\begingroup\$ I'll mark this as accepted tomorrow in case other solutions come in. Extremely helpful in all areas! Thank you very much. \$\endgroup\$Luminous– Luminous2022年12月07日 19:12:51 +00:00Commented Dec 7, 2022 at 19:12
Rather than listing the mappings in a separate data structure you could use a custom attribute to decorate/annotate each enum value with the mapTo information.
enum WorkflowEventHandler
{
[WorkflowEventHandlerMap(typeof(ConsecutiveCaseCreated))]
CONSECUTIVE_CASE_CREATED = 1,
[WorkflowEventHandlerMap(typeof(ConsecutiveCaseCancelled))]
CONSECUTIVE_CASE_CANCELLED = 2,
[WorkflowEventHandlerMap(typeof(ConsecutiveCaseModified))]
CONSECUTIVE_CASE_MODIFIED = 3,
[WorkflowEventHandlerMap(typeof(IntermittentCaseCreated))]
INTERMITTENT_CASE_CREATED = 4,
[WorkflowEventHandlerMap(typeof(IntermittentCaseTimeAdjusted))]
INTERMITTENT_CASE_TIME_ADJUSTED = 5,
[WorkflowEventHandlerMap(typeof(IntermittentCaseTimeDeleted))]
INTERMITTENT_CASE_TIME_DELETED = 6,
[WorkflowEventHandlerMap(typeof(CaseChangedType))]
CASE_CHANGED_TYPE = 7
}
The definition of the WorkflowEventHandlerMapAttribute
can be as simple as this
[AttributeUsage(AttributeTargets.Field)]
class WorkflowEventHandlerMapAttribute: Attribute
{
public Type MapTo { get; init; }
public WorkflowEventHandlerMapAttribute(Type MapTo) => MapTo = mapTo;
}
With reflection you can retrieve the related attribute instance like this:
var handler = WorkflowEventHandler.CONSECUTIVE_CASE_CREATED;
var attribute = typeof(WorkflowEventHandler)
.GetField(handler.ToString())
.GetCustomAttributes(typeof(WorkflowEventHandlerMapAttribute), false)
.FirstOrDefault() as WorkflowEventHandlerMapAttribute;
And finally you could use the attribute.MapTo
to get the ConsecutiveCaseCreated
type for the above handler
.
Since you're using AutoMapper
, I'm sure you've already done your mapping profiles. For that, you can just extend your work by using custom destination conversion, by using either ConvertUsing
or ConstructUsing
. This would be also possible since all classes are derived from WebhookActivity
.
You can create a model to hold both command
and WorkflowEventHandler
values, then using AutoMapper
just map this model and use the switch
inside ConvertUsing
.
AutoMapper configuration example:
public class WebhookActivityRequestModel
{
public WebhookActivity? Source { get; set; }
public WorkflowEventHandler Name { get; set; }
public WebhookActivityRequestModel() { }
public WebhookActivityRequestModel(WebhookActivity? source, WorkflowEventHandler name)
{
Source = source;
Name = name;
}
}
public class WorkflowEventHandlerProfile : Profile
{
public WorkflowEventHandlerProfile()
{
CreateMap<WebhookActivity, ConsecutiveCaseCreated>();
CreateMap<WebhookActivity, ConsecutiveCaseModified>();
CreateMap<WebhookActivityRequestModel, WebhookActivity>()
.ConvertUsing((source, dest, context) =>
{
return source.Name switch
{
WorkflowEventHandler.CONSECUTIVE_CASE_CREATED => context.Mapper.Map<ConsecutiveCaseCreated>(source.Source),
WorkflowEventHandler.CONSECUTIVE_CASE_MODIFIED => context.Mapper.Map<ConsecutiveCaseModified>(source.Source),
_ => context.Mapper.Map<ConsecutiveCaseCreated>(source.Source)
};
});
}
}
Now, your method would be something like this:
public WebhookActivity MakeMessage(WebhookActivityRequest request)
{
WebhookActivity command = _mapper.Map<ConsecutiveCaseCreated>(request);
int mappingId = mappings
.FirstOrDefault(m => request.WorkflowCode == m.WorkflowCode)?.EventHandlerId ?? 0;
if(Enum.TryParse(typeof(WorkflowEventHandler), mappingId.ToString(), out var result))
{
var model = new WebhookActivityRequestModel(command, result);
return _mapper.Map<WebhookActivityRequestModel, WebhookActivity>(model);
}
return command;
}
-
\$\begingroup\$ I like this solution for moving the actual map calls to the IMapper Profile configuration. Also my class handling WebhookActivityRequest no longer needs to know about the dictionary of delegates. \$\endgroup\$Luminous– Luminous2022年12月09日 14:30:08 +00:00Commented Dec 9, 2022 at 14:30
-
\$\begingroup\$ @Luminous you could also create a custom converter that implements
ITypeConverter
and move the switch logic inside it and then call it in the profile likeConvertUsing(new WorkflowEventHandlerConverter())
this would add a better maintainability as well. \$\endgroup\$iSR5– iSR52022年12月09日 14:49:10 +00:00Commented Dec 9, 2022 at 14:49
CreateMap<WebhookActivity, ConsecutiveCaseCreated>();
\$\endgroup\$Convert.ChangeType
? learn.microsoft.com/en-us/dotnet/api/… \$\endgroup\$