4
\$\begingroup\$

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.

Peter Csala
10.7k1 gold badge16 silver badges36 bronze badges
asked Dec 7, 2022 at 17:06
\$\endgroup\$
4
  • \$\begingroup\$ Can you modify the enum? If so you could define a custom attribute to store the mapping between enum values and types. Then you could use reflection to perform the mapping lookup. \$\endgroup\$ Commented Dec 7, 2022 at 18:04
  • 1
    \$\begingroup\$ @PeterCsala I do own the enum so yes I can modify it. I do use the integer value of the enum in other places though. Your custom attribute to define the mapping sounds interesting. Wouldn't know how to implement it though. Each mapping is done in a single class extending Automapper.Profile. ex: CreateMap<WebhookActivity, ConsecutiveCaseCreated>(); \$\endgroup\$ Commented Dec 7, 2022 at 19:09
  • \$\begingroup\$ couldn't quite figure out how to make the call to IMapper.Map() => make delegates. \$\endgroup\$ Commented Dec 7, 2022 at 22:58
  • \$\begingroup\$ have you looked into Convert.ChangeType? learn.microsoft.com/en-us/dotnet/api/… \$\endgroup\$ Commented Dec 8, 2022 at 12:53

3 Answers 3

5
\$\begingroup\$

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);
}
answered Dec 7, 2022 at 17:48
\$\endgroup\$
1
  • \$\begingroup\$ I'll mark this as accepted tomorrow in case other solutions come in. Extremely helpful in all areas! Thank you very much. \$\endgroup\$ Commented Dec 7, 2022 at 19:12
4
\$\begingroup\$

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.


Here you can find a working example on dotnet fiddle.

answered Dec 7, 2022 at 20:57
\$\endgroup\$
4
\$\begingroup\$

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;
}
answered Dec 7, 2022 at 23:42
\$\endgroup\$
2
  • \$\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\$ Commented 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 like ConvertUsing(new WorkflowEventHandlerConverter()) this would add a better maintainability as well. \$\endgroup\$ Commented Dec 9, 2022 at 14:49

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.