MVC Ajax с созданием динамического частичного просмотра

Как создать динамические ajax.actionlinks, которые будут вызывать динамические частичные представления.

Например:

  • У меня есть страница, которая будет генерировать x количество комментариев
  • Каждый комментарий может быть проголосован вверх или вниз (индивидуально)
  • Количество голосов и вниз голосов подсчитывается в одно целое
  • Каждый комментарий div будет иметь свою собственную ajax.actionlink
  • Каждая ajax.actionlink передает контроллеру идентификатор комментария
  • Контроллер рассчитает общее количество голосов и вызовет частичное представление для отображения обратно в div с правильным идентификатором.

Что я сделал до сих пор:

  • Я смог создать успешную ajax.actionlink

  • Это вызовет контроллер и суммирует голоса

  • Это вызовет частичное представление и отобразит голоса

В чем проблема

  • Я не хочу жестко кодировать 30-100 различных ajax.actionlinks для вызова 30-100 жестких кодированных частичных представлений.

Как я могу выполнить это динамически?

Существующий код:

Моя ajax.actionlink внутри моего бритва

 @Html.Raw(Ajax.ActionLink("[replacetext]", "VoteUp",
                new { UserPostID = @Model.Id },
                        new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "CountVote" }).ToHtmlString().Replace("[replacetext]",
                        "<img src=\"/Images/up_32x32.png\" />"))

Мой div внутри того же вида бритвы, чтобы отобразить возвращаемые результаты из частичного представления.

<div id="CountVote" class="postvotes"></div>

Мой контроллер

    public PartialViewResult VoteUp(int UserPostID)
    {
        try
        {
            UserVotes vote = new UserVotes();
            vote.SubmitedVote = 1;
            vote.UserId = Convert.ToInt32(Session["id"]);
            vote.UserPostID = UserPostID;
            ViewBag.SumVotes = postRepository.InsertUserPostVote(vote);

        }
         catch (Exception e)
        {
            xxx.xxx.xxxx().Raise(e);
        }
        return PartialView("_TotalVotes");
    }

И, наконец, мое частичное представление (_TotalVotes.cshtml)

@ViewBag.SumVotes

Теперь мой основной вид Viewpost показывает комментарии в цикле, используя viewbag.

foreach (var item in (List<UserComment>)ViewData["Comments"])
            {
                CommentVote = "cv" + i.ToString();
    <div class="postlinewrapper">
        <div class="postvotesframe">
            <div class="postvotes">
                @Html.Raw(Ajax.ActionLink("[replacetext]", "VoteUp",
                        new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "CountVote" }).ToHtmlString().Replace("[replacetext]",
                        "<img src=\"/Images/up_32x32.png\" />"))
            </div>

            <div id="@CommentVote" class="@CommentVote">0</div>
            <div class="postvotes">
                @Html.Raw(Ajax.ActionLink("[replacetext]", "VoteDown",
                        new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "CountVote" }).ToHtmlString().Replace("[replacetext]",
                        "<img src=\"/Images/down_32x32.png\" />"))
            </div>
        </div>
        <div class="postleftbar">
            @Html.Raw(item.Comment)
        </div>
        <div class="postrightbar">
            <div>
                <div class="post_spec">
                    <div class="post_spec_title">Call Sign:  </div>
                    <div class="post_spec_detail">@item.CallSign</div>
                </div>
                <div class="post_spec">
                    <div class="post_spec_title">When:  </div>
                    <div class="post_spec_detail">@item.CommentDate.ToString("dd/MM/yyyy")</div>
                </div>
            </div>
            <br />
            <br />
        </div>
    </div>
                i += 1;
            }

Я внедрил логин для увеличения или уменьшения голосов вверх и вниз:

 public PartialViewResult VoteUp(int userPostId)
        {
            try
            {
                UserVotes vote = new UserVotes();
                vote.SubmitedVote = 1;
                vote.UserId = Convert.ToInt32(Session["id"]);
                vote.UserPostID = userPostId;
                ViewBag.SumVotes = postRepository.InsertUserPostVote(vote);

            }
             catch (Exception e)
            {
                xxxx.xxxx.xxxx().Raise(e);
            }
            return PartialView("_TotalVotes");
        }

        public PartialViewResult VoteDown(int userPostId)
        {
            try
            {
                UserVotes vote = new UserVotes();
                vote.SubmitedVote = -1;
                vote.UserId = Convert.ToInt32(Session["id"]);
                vote.UserPostID = userPostId;
                ViewBag.SumVotes = postRepository.InsertUserPostVote(vote);

            }
            catch (Exception e)
            {
                xxx.xxxx.xxxx().Raise(e);
            }
            return PartialView("_TotalVotes");
        }

Теперь весь этот код работает для 1 ajax-вызова просто отлично, но мне нужно отображать отдельные вызовы ajax для отдельных divs динамически.

Ответы

Ответ 1

Попробуйте это.

Основной вид

Я предполагаю, что у вас есть модель с свойством collection Comments элементов Comment

@model MyNamespace.CommentAndOtherStuff

<ul>
    @foreach(item in Model.Comments)
    {
      <li>
          <a href="@Url.Action("VoteUp", "VoteControllerName", new { UserPostId = item.Id })" 
             class="vote-link"
             data-id="@item.Id">@item.Votes</a><img src="vote.jpg" />
      </li>
    }
</ul>

И ваш контроллер просто возвращает класс под названием VoteResult как JSON.

[HttpPost]
public ActionResult VoteUp(int UserPostID)
{
    ...
    var model = new VoteResult
    {
        UserPostID = UserPostID,
        Votes = service.tallyVote(UserPostID)
    };

    return Json(model);
}

Теперь зацепите всех из них обработчиком событий jQuery и настройте вызов AJAX

$(document).ready(function() {

    $("a.vote-link").on("click", function(event) {
        event.preventDefault();
        var link = $(this);  // the link instance that was clicked
        var id = link.attr("data-id");
        var url = link.attr("href");

        $.ajax({
            url: url,
            type: "post"
        })
        .done(function(result) {
            // JSON result: { UserPostID: 1, Votes: 5 }

            // replace link text
            link.html(result.Votes);
        });
    });

});

Но мне нужен фрагмент html с частичным представлением.

[HttpPost]
public ActionResult VoteUp(int UserPostID)
{
    ...
    var model = new VoteResult
    {
        UserPostID = UserPostID,
        Votes = service.tallyVote(UserPostID)
    };

    return PartialView("_TotalVotes", model);
}

_TotalVotes partial

@model MyNamespace.VoteResult

@if (Model.Votes < 0)
{
    <span class="unpopular">@Model.Votes</span>
}
else
{
    <span class="awesome">@Model.Votes</span>
}

И настройте обратный вызов AJAX

.done(function(result) {
    link.html(result);
});

Теперь вы можете написать помощник для фрагмента ссылки, но он, по-моему, смущает вещи (это решение). Все, что вам действительно нужно, это имя класса и идентификатор данных, который будет связывать ваш javascript.

Ответ 2

Использование Ajax помощников здесь кажется ненужным накладными расходами, и я предлагаю вам просто использовать jquery-методы для обновления DOM. Ваш текущий код подсказывает, что вам может не хватать какой-либо логики, чтобы сделать работу системы голосования с комментариями, включая указание того, какие действия пользователь может выполнить уже. Например (и если вы хотите, чтобы он работал аналогично SO), если пользователь ранее голосовал, тогда нажатие на ссылку для голосования должно уменьшать количество голосов на 1, но нажатие на нисходящую ссылку должно уменьшаться подсчет голосов на 2 (предыдущий проголосовавший плюс новый проголосовавший).

Обратитесь к этой скрипте о том, как это может быть написано и вести себя при нажатии элементов голосования

Ваша модель представления комментария может выглядеть как

public enum Vote { "None", "Up", "Down" }
public class CommentVM
{
  public int ID { get; set; }
  public string Text { get; set; }
  public Vote CurrentVote { get; set; }
  public int TotalVotes { get; set; }
}

и предположим, что у вас есть модель, содержащая коллекцию комментариев

public class PostVM
{
  public int ID { get; set; }
  public string Text { get; set; }
  public IEnumerable<CommentVM> Comments { get; set; }
}

и связанный с ним DisplayTemplate

/Views/Shared/DisplayTemplates/CommentVM.cshtml

@model CommentVM
<div class="comment" data-id="@Model.ID" data-currentvote="@Model.CurrentVote">
  <div class="vote">
    <div class="voteup" class="@(Model.CurrentVote == Vote.Up ? "current" : null)"></div>
    <div class="votecount">@Model.TotalVotes</div>
    <div class="votedown" class="@(Model.CurrentVote == Vote.Down ? "current" : null)"></div>
  </div>
  <div class="commenttext">@Html.DisplayFor(m => m.Text)</div>
</div>

Затем в главном представлении

@model PostVM
.... // display some properties of Post?
@Html.DisplayFor(m => m.Comments)

<script>
  var voteUpUrl = '@Url.Action("VoteUp")';
  var voteDownUrl = '@Url.Action("VoteDown")';
  $('.voteup').click(function() {
    var container = $(this).closest('.comment');
    var id = container.data('id');
    var voteCount = new Number(container.find('.votecount').text());
    $.post(voteUpUrl, { id: id }, function(response) {
      if (!response) {
        // oops, something went wrong - display error message?
        return;
      }
      container.find('.votecount').text(response.voteCount); // update vote count
      if (response.voteCount < voteCount) {
        // the user previously upvoted and has now removed it
        container.find('.voteup').removeClass('current');
      } else if (response.voteCount == voteCount + 1) {
        // the user had not previously voted on this comment
        container.find('.voteup').addClass('current');
      } else if (response.voteCount == voteCount + 2) {
        // the user previoulsy down voted
        container.find('.votedown').removeClass('current');
        container.find('.voteup').addClass('current');
      }
    });
  });
  $('.votedown').click(function() {
    ... // similar to above (modify logic in if/elseif blocks)
  });

</script>

и метод контроллера

public JsonResult VoteUp(int id)
{
  int voteCount = // your logic to calculate the new total based on the users current vote (if any) for the comment
  return Json(new { voteCount = voteCount });
}