2012-06-29 7 views
2

Следующий код был исчерпан, но в основном я хотел бы получить следующее:Как создать индексы инкрементной префиксной коллекции с помощью шаблонов HTML MVC?

Я хотел бы иметь возможность редактировать вопросы и их содержащие ответы на выбор, будучи в состоянии динамически добавлять/remove Вопросы/ответы на странице. В идеале, HtmlFieldPrefix для моих элементов будет не последовательным, но Html.EditorFor() использует последовательный индекс.

У меня есть вопрос ViewModel, который содержит IEnumerable из вариантов ответов:

public class QuestionViewModel 
{ 
    public int QuestionId { get; set; } 
    public IEnumerable<AnswerChoiceViewModel> AnswerChoices { get; set; } 
} 

в моем вопросе частичный вид (Question.ascx), у меня есть это:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.QuestionViewModel>" %> 

<%=Html.HiddenFor(m => m.QuestionId)%> 
<%=Html.EditorFor(m => m.AnswerChoices) %> 

и ответ Выбор шаблон редактора (AnswerChoiceViewModel.ascx):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.AnswerChoiceViewModel>" %> 

<%=Html.HiddenFor(m => m.AnswerChoiceId)%> 
<%=Html.TextBoxFor(m => m.Name)%> 

Когда я выдаю Question.ascx , То выход будет выглядеть следующим образом:

<input type="hidden" id="QuestionId" value="1" /> 
<input type="hidden" id="Question.AnswerChoices[0].AnswerChoiceId" value="1" /> 
<input type="hidden" id="Question.AnswerChoices[0].Name" value="Answer Choice 1" /> 

<input type="hidden" id="QuestionId" value="2" /> 
<input type="hidden" id="Question.AnswerChoices[1].AnswerChoiceId" value="2" /> 
<input type="hidden" id="Question.AnswerChoices[1].Name" value="Answer Choice 2" /> 

То, что я хочу знать, как я могу обеспечить EditorFor пользовательский индекс GUID так, что страница будет оказывать так:

<input type="hidden" id="QuestionId" value="1" /> 
<input type="hidden" id="Question.AnswerChoices[e1424d5e-5585-413c-a1b0-595f39747876].AnswerChoiceId" value="1" /> 
<input type="hidden" id="Question.AnswerChoices[e1424d5e-5585-413c-a1b0-595f39747876].Name" value="Answer Choice 1" /> 

<input type="hidden" id="QuestionId" value="2" /> 
<input type="hidden" id="Question.AnswerChoices[633db1c3-f1e6-470b-9c7f-c138f2d9fa71].AnswerChoiceId" value="2" /> 
<input type="hidden" id="Question.AnswerChoices[633db1c3-f1e6-470b-9c7f-c138f2d9fa71].Name" value="Answer Choice 2" /> 

Я уже писал вспомогательный метод, который получит префиксный индекс текущего контекста и сохранит его в скрытом поле «.Index», чтобы некорректные индексы могли быть связаны правильно. Просто хочу знать, как EditorFor назначает индексы, чтобы я мог переопределить его (или любое другое рабочее решение).

ответ

0

Html.EditorFor не является чем-то другим, так называемым Html-вспомогательным методом, который отображает input со всеми подходящими атрибутами.

Единственное решение, которое приходит мне на ум, - написать собственный. Это должно быть довольно просто - 5-10 строк. Взгляните на это Creating Custom Html Helpers Mvc.

0

Стив Сандерсон предоставил simple implementation, который может делать то, что вы ищете. Я недавно начал использовать его сам; он не идеален, но он действительно работает. К сожалению, вам нужно немного намазать магией, чтобы использовать его метод BeginCollectionItem; Я пытаюсь обходить это самостоятельно.

2

В то время как я занимался этой проблемой и попал в сообщение от С. Сандерсона (создателя Knockoutjs), где он описал и решил аналогичную проблему. Я использовал часть своего кода и пытался изменить его в соответствии с моими потребностями. Я помещаю код ниже в некоторый класс (exapmle: Helpers.cs), добавляем пространство имен в web.config.

#region CollectionItem helper 
    private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_"; 

    public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName) 
    { 
     var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName); 
     string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString(); 

     // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync. 
     html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, itemIndex)); 

     return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex)); 
    } 

    public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix) 
    { 
     return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix); 
    } 

    private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName) 
    { 
     // We need to use the same sequence of IDs following a server-side validation failure, 
     // otherwise the framework won't render the validation error messages next to each item. 
     string key = idsToReuseKey + collectionName; 
     var queue = (Queue<string>)httpContext.Items[key]; 
     if (queue == null) 
     { 
      httpContext.Items[key] = queue = new Queue<string>(); 
      var previouslyUsedIds = httpContext.Request[collectionName + ".index"]; 
      if (!string.IsNullOrEmpty(previouslyUsedIds)) 
       foreach (string previouslyUsedId in previouslyUsedIds.Split(',')) 
        queue.Enqueue(previouslyUsedId); 
     } 
     return queue; 
    } 

    private class HtmlFieldPrefixScope : IDisposable 
    { 
     private readonly TemplateInfo templateInfo; 
     private readonly string previousHtmlFieldPrefix; 

     public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix) 
     { 
      this.templateInfo = templateInfo; 

      previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix; 
      templateInfo.HtmlFieldPrefix = htmlFieldPrefix; 
     } 

     public void Dispose() 
     { 
      templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix; 
     } 
    } 

    #endregion 

После вы можете иметь EditorTemplate или частичное как этот

@using (Html.BeginCollectionItem("AnswerChoices")) 
{ 
@Html.HiddenFor(m => m.AnswerChoiceId) 
@Html.TextBoxFor(m => m.Name) 
} 

и перечислить через шаблон списка рендеринга (частично).

0

Другой вариант переопределить идентификатор атрибута, как это:

@Html.TextBoxFor(m => m.Name, new { id = @guid })

1

Потребовалось мне путь дольше, чем это необходимо, чтобы понять это.У всех слишком много работы, чтобы сделать это. Секрет соус эти четыре строки кода:

 @{ 
 
      var index = Guid.NewGuid(); 
 
      var prefix = Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, @"^(.+)\[\d+\]$").Groups[1].Captures[0].Value; 
 
      //TODO add a ton of error checking and pull this out into a reusable class!!!! 
 
      ViewData.TemplateInfo.HtmlFieldPrefix = prefix + "[" + index + "]"; 
 
     } 
 
     <input type="hidden" name="@(prefix).Index" value="@index"/>

Теперь, что это делают? Мы получаем новый guid, это наш новый индекс для замены целочисленного, который автоматически назначается. Затем мы получим префикс поля по умолчанию, и мы отменим этот индекс int, который нам не нужен. После подтверждения того, что мы создали некоторый технический долг, мы затем обновляем viewdata, чтобы все вызовы editorfor теперь использовали это как новый префикс. Наконец, мы добавим ввод, который будет отправлен обратно в привязку модели, указав индекс, который он должен использовать, чтобы связать эти поля вместе.

Где эта магия должна произойти? Внутри шаблона редактора: /Views/Shared/EditorTemplates/Phone.cshtml

@using TestMVC.Models 
 
@using System.Text.RegularExpressions 
 
@model Phone 
 
    <div class="form-horizontal"> 
 
     <hr /> 
 
     @{ 
 
      var index = Guid.NewGuid(); 
 
      var prefix = Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, @"^(.+)\[\d+\]$").Groups[1].Captures[0].Value; 
 
      //TODO add a ton of error checking and pull this out into a reusable class!!!! 
 
      ViewData.TemplateInfo.HtmlFieldPrefix = prefix + "[" + index + "]"; 
 
     } 
 
     <input type="hidden" name="@(prefix).Index" value="@index"/> 
 
     <div class="form-group"> 
 
      @Html.LabelFor(model => model.Number, htmlAttributes: new { @class = "control-label col-md-2" }) 
 
      <div class="col-md-10"> 
 
       @Html.EditorFor(model => model.Number, new { htmlAttributes = new { @class = "form-control" } }) 
 
       @Html.ValidationMessageFor(model => model.Number, "", new { @class = "text-danger" }) 
 
      </div> 
 
     </div> 
 

 
     <div class="form-group"> 
 
      @Html.LabelFor(model => model.IsEnabled, htmlAttributes: new { @class = "control-label col-md-2" }) 
 
      <div class="col-md-10"> 
 
       <div class="checkbox"> 
 
        @Html.EditorFor(model => model.IsEnabled) 
 
        @Html.ValidationMessageFor(model => model.IsEnabled, "", new { @class = "text-danger" }) 
 
       </div> 
 
      </div> 
 
     </div> 
 

 
     <div class="form-group"> 
 
      @Html.LabelFor(model => model.Details, htmlAttributes: new { @class = "control-label col-md-2" }) 
 
      <div class="col-md-10"> 
 
       @Html.TextAreaFor(model => model.Details, new { htmlAttributes = new { @class = "form-control" } }) 
 
       @Html.ValidationMessageFor(model => model.Details, "", new { @class = "text-danger" }) 
 
      </div> 
 
     </div> 
 
    </div>

EditorTemplate? Какие?! Как?! Просто поместите его в указанную выше директорию, используя имя объекта для имени файла. Пусть конвенция MVC работает своей магией. От основного зрения просто добавить редактор для этой IEnumerable собственности:

<div class="form-group"> 
 
@Html.LabelFor(model => model.Phones, htmlAttributes: new { @class = "control-label col-md-2" }) 
 
<div class="col-md-10"> 
 
    @Html.EditorFor(model => model.Phones, new { htmlAttributes = new { @class = "form-control" } }) 
 
</div> 
 
</div>

Теперь обратно в контроллер, убедитесь, что вы обновить подпись метода принять, что IEnumerable (Bind включают телефоны):

 [HttpPost] 
    [ValidateAntiForgeryToken] 
    public ActionResult Create([Bind(Include = "ContactId,FirstName,LastName,Phones")] Contact contact) 
    { 
     if (ModelState.IsValid) 
     { 

      db.Contacts.Add(contact); 
      db.SaveChanges(); 
      //TODO need to update this to save phone numbers 
      return RedirectToAction("Index"); 
     } 

     return View(contact); 
    } 

Как добавлять и удалять их на странице? Добавьте несколько кнопок, привяжите некоторый JavaScript, добавьте метод к контроллеру, который вернет представление для этой модели. Ajax, чтобы схватить его и вставить в страницу. Я позволю вам разобраться с этими деталями, поскольку на данный момент это просто занятая работа.