Micha Schopman
Micha Schopman

Reputation: 221

switch/case on viewmodel in controller, any refactoring advice?

I could use some advice on refactoring. In my application users are able to dynamically add new form fields; customfield. For each type (text, dropdown, checkbox, etc.) a ViewModel (TextBoxViewModel, DropDownViewModel, CheckboxViewModel, etc.) is defined.

When I post a form, the appropriate Edit action is executed and I read each customfield to store their values.

Currently the implementation works but is ugly; I switch/case/if/else through all ViewModel types and based on the type I execute the required logic.

This is the the current implementation:

private static void MapToModel(Ticket ticket, TicketViewModel model)
    {
        ticket.Id = model.Id;
        ticket.Name = model.Name;

        ticket.Attributes.Clear();
        foreach (var cvm in model.Controls)
        {
            var attribute = new TicketAttribute
            {
                Id = cvm.Id,
                Name = cvm.Name,
            };

            if (cvm is TextBoxViewModel)
            {
                attribute.Value = ((TextBoxViewModel) cvm).Value;
            }else if (cvm is DropDownListViewModel)
            {
                attribute.Value = ((DropDownListViewModel)cvm).Values;
            }
            ticket.Attributes.Add(attribute);
        }
    }

And I would like to refactor this to something like this, but without putting all logic in the ViewModel. Best I could come up with is the visitor pattern where I would add a Accept method to the ViewModel class, and use visitors to execute the logic required:

This would still require the same switching logic on types in the AddAttribute method:

foreach (var cvm in model.Controls)
    {
        ticket.Attributes.AddAttribute(cvm);
    }

This would require logic in the ViewModel class

foreach (var cvm in model.Controls)
    {
        ticket.Attributes.Add(cvm.AddAttribute);
    }

I want to refactor this to create a more generic approach, so that in future when new types of fields are added I don't have to update all codes with new constructions to check for types.

[solution after the provided help]

I had to cast the object, I cannot use different returntypes in different implementations of IControlViewModel so that is one part I have to work around, but overall this is beautiful.

  ticket.Attributes = model.Controls
    .OfType<IControlViewModel>()
    .Select(cvm => new TicketAttribute {
        Id = cvm.Id,
        Name = cvm.Name,
        Value = (string)cvm.OutputValue
        })
    .ToList();


  public interface IControlViewModel
    {
        string Id { get; }
        string Name { get; }
        object OutputValue { get; }
    }

    public abstract class ControlViewModel : IControlViewModel
    {
        public string Id { get; set; }
        public abstract string Type { get; }
        public string Label { get; set; }
        public string Name { get; set; }
        public bool Visible { get; set; }
        public abstract object OutputValue { get; }
    }

    public class TextBoxViewModel : ControlViewModel
    {
        public override string Type
        {
            get { return "textbox"; }
        }
        public override object OutputValue
        {
            get
            {
                return Value;
            }
        }

        public string Value {set; }
     }

Upvotes: 0

Views: 720

Answers (1)

Kaspars Ozols
Kaspars Ozols

Reputation: 7017

1) Create an interface which defines that you will have output value property on each of your view models

public interface IControlViewModel
{
    object OutputValue{get;}
}

2) Implement interface in each of your viewmodels:

public TextBoxViewModel: IControlViewModel
{
    ...
    public object OutputValue
    {
        get 
        {   
            //return whatever is your expected output value from control
            return Value; 
        }
    }
    ...
}

3) Then you can get all attributes with this single LINQ statement:

ticket.Attributes = model.Controls
    .OfType<IControlViewModel>()
    .Select(cvm => new TicketAttribute {
        Id = cvm.Id,
        Name = cvm.Name,
        Value = cvm.OutputValue
        })
    .ToList();

4) This code will work fine even if you create new control types, just make sure to implement interface in your new viewmodels.

Upvotes: 2

Related Questions