I've been looking into model binding to resolve a specific issue I'm having. I tried some methods described in various blogs and stackoverflow answers but I'm not really getting there.
First I'll present my model:
public class CampaignModel
{
[Required]
[StringLength(24)]
[Display(Name = "CampaignModel_Name", Prompt = "CampaignModel_Name", ResourceType = typeof(CampaignResources))]
public string Name { get; set; }
[StringLength(255)]
[Display(Name = "CampaignModel_Description", Prompt = "CampaignModel_Description", ResourceType = typeof(CampaignResources))]
public string Description { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name = "CampaignModel_StartDate", ResourceType = typeof(CampaignResources))]
public DateTime StartDate { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name = "CampaignModel_EndDate", ResourceType = typeof(CampaignResources))]
public DateTime EndDate { get; set; }
[Display(Name = "CampaignModel_Tags", Prompt = "CampaignModel_Tags", ResourceType = typeof(CampaignResources))]
public TagList Tags { get; set; }
}
So this is a fairly basic model except for the last property TagList. Now in the future allot of my models will be having a TagList so this is fairly important to me. A TagList is just this:
public class TagList : List<Tag>
{
}
I created this class to easily be able to create an EditorTemplate for it without having to place UIHint attributes. Now I'm using the select2.js library for my taglist editor, which handles ajax searching in existing tags and so forth. The issue here is that Select2 binds to a single hidden field where it seperates the various tag values by a , and depending whether it is an existing or a new tag it uses the text or id producing a list like 1,tag,34,my new tag. This input I would like to translate into a TagList.
So the concrete question is: How would I model bind this single hidden input into the TagList property on my Model and be able to reuse this behaviour for all my models easily?
EDIT => Adding the code of the EditorTemplate and The ModelBinder I created based off the answer of Andrei
My EditorTemplate (omitted js)
<div class="form-group">
@Html.Label(string.Empty, ViewData.ModelMetadata.DisplayName, new { @class = "col-md-3 control-label" })
<div class="col-md-9">
@Html.Hidden(string.Empty, ViewData.TemplateInfo.FormattedModelValue, new { @class = "form-control select2" })
</div>
</div>
My ModelBinder (set as DefaultModelBinder in global.asax)
public class TagListModelBinder : DefaultModelBinder
{
protected override void BindProperty(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(TagList))
{
ValueProviderResult value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
string[] rawTags = value.AttemptedValue.Split(',');
List<long> tagIds = new List<long>();
TagList tags = new TagList();
foreach (string rawTag in rawTags)
{
long id;
if (long.TryParse(rawTag, out id))
{
// Existing tags need to be retrieved from DB
tagIds.Add(id);
}
else
{
// New tags can simply be added without ID
tags.Add(new Tag { Text = rawTag });
}
}
if (tagIds.Count > 0)
{
using (TagServiceClient client = new TagServiceClient())
{
List<Services.TagService.Tag> existingTags = client.GetTagsByIds(tagIds);
tags.AddRange(existingTags.Select(t => new Tag { Id = t.id, Text = t.text }));
}
}
propertyDescriptor.SetValue(bindingContext.Model, tags);
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}