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
ITypeConverterand 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\$