Reputation: 221
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
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