2014-04-02 210 views
2

我一直在寻找模型绑定来解决我遇到的具体问题。我尝试了一些在各种博客和计算器的答案中描述的方法,但我并没有真正到达那里。复杂的ASP.NET MVC绑定

首先,我会提出我的模型:

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; } 
} 

因此,这是除了最后一个属性标记列表一个相当基本的模型。现在将来,我的模型将会有一个TagList,所以这对我来说相当重要。标记表仅仅是这样的:

public class TagList : List<Tag> 
{ 
} 

我创造了这个类能够轻松而不必把UIHint属性为它创建一个EditorTemplate。现在我为我的taglist编辑器使用select2.js库,它处理现有标签中的ajax搜索等等。这里的问题是Select2绑定到一个隐藏字段,它将a分隔各个标签值,并根据它是现有标签还是新标签使用文本或ID生成列表,如1,tag,34,my new tag。这个输入我想翻译成TagList。

所以具体的问题是: 我该如何模型绑定这个单一的隐藏的输入到我的模型的TagList属性,并能够轻松地重用这种行为为我所有的模型?

EDIT =>添加EditorTemplate和该模型绑定器的代码我创建基于关安德烈的答案

我EditorTemplate(省略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> 

我的模型绑定器(集作为global.asax中的DefaultModelBinder)

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); 
    } 

回答

3

最好的方法是继承DefaultModelBinder并检查你处理的是哪个属性。如果是TagList类型 - 请继续并应用您所需的任何逻辑。否则,让默认实现来完成这项工作。

public class TagListModelBinder : DefaultModelBinder 
{ 
    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor) 
    { 
     if (propertyDescriptor.PropertyType == typeof(TagList)) 
     { 
      ValueProviderResult value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name); 
      string[] rawTags = value.ToString().Split(','); 

      TagList tags = new TagList(); 
      foreach (string rawTag in rawTags) 
      { 
       // for numbers - get them from DB 
       // for strings - create new and store in DB 
       // then add them to tags 
      } 

      propertyDescriptor.SetValue(bindingContext.Model, tags); 
     } 
     else 
     { 
      base.BindProperty(controllerContext, bindingContext, propertyDescriptor) 
     } 
    } 
} 
+0

我应该为标记列表类型注册此类型DefaultModelBinder或特别? – IvanL

+0

@IvanL,因为这种粘合剂不不会干扰任何其他类型,只需替换默认值即可:'ModelBinders.Bind ers.DefaultBinder = new TagListModelBinder();' – Andrei

+0

我试过解决方案,但我遇到了异常“参数从类型'System.String'转换为类型'ROCME.EventDrive.Backend.Models.Shared.Tag'失败因为没有类型转换器可以在这些类型之间进行转换。“尽管我发现预期的绑定发生了。此外,我需要以不同的方式处理ValueProviderResult,因为.ToString()会导致类型名称而不是实际使用的值。 – IvanL

0

如果您将此数据重新发布到ajax调用中,您可以准备数据,以便将其绑定到您的模型属性TagList。

我假设标签类的结构是

public class Tag 
{ 
    public string Name {get; set;} 
    public string Id {get; set;} 
} 

虽然调用Ajax,你可以做

  1. 拆分上逗号隐藏字段的值(,)
  2. 将每个标签的值加上

    分割列表中的每个标签

    data.push(“name”:“TagList [count]。名称”, “值”, “名称绑定”);

    data.push( “名称”: “标记表第[count] .ID”, “值”, “标识绑定”);

    算上++

而且随着Ajax请求发布此数据,并希望这应该绑定到你的模型。

+0

我没有使用ajax发布我的数据,它是直接表单提交 – IvanL