将视图组件与表单一起使用

Use of View Components with forms

我正在尝试学习 ASP.NET 核心 MVC 中视图组件的正确用法,所以我有以下示例:我的想法是渲染一个包含电影细节的视图,在其中 ReviewViewComponent 持有 10 星电影评级小部件。

视图组件的视图内部是一个带有单选按钮的表单。表单操作名称被传递给视图组件(CreateEdit,取决于用户是否已经给出评级)。根据收到的动作名称,视图组件内的表单将调用 ReviewsController.

内的 CreateEdit 方法

这一切都有效,直到我到达 ReviewsController 中的 return 调用。我希望能够 return 那里的视图组件,并简单地在 div 中渲染 return 结果,使用 AJAX 在详细信息中使用 id="now-showing-details-rating-div"。这适用于部分视图(代码在 ReviewsControllerEdit 方法中被注释掉),但它似乎不适用于视图组件(它只是将视图组件的视图呈现为一个全新的视图,即使我在表单上调用 AJAX 的方式与在局部视图中调用时的方式相同)。

我实际上是在滥用视图组件的概念吗?在表单提交后呈现视图的一部分的意义上,仅使用部分视图实际上更好吗?

Ajax 片段

$.ajax({
    type: "POST",
    url: requestUrl,
    data: form.serialize(),
    success: function (data) {
        $("#" + divZaRezultat).html(data);
    }
});

视图模型:

public class MovieDetailsVM
{
    public string Title { get; set; }
    public int Id { get; set; }
    public int Year { get; set; }
    public string Actors { get; set; }
    public string Country { get; set; }
    public string Directors { get; set; }
    public int Duration { get; set; }
    public string VideoLink { get; set; }
    public string AverageRating { get; set; }
    public string NumberOfReviews { get; set; }
    public ReviewIndexVM CurrentUserReview { get; set; }
}

public class ReviewIndexVM
{
    public int ReviewId { get; set; }
    public int Rating { get; set; }
    public MasterModel User { get; set; }
    public MasterModel Movie { get; set; }
}

ViewComponent

public class ReviewViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(string methodName, ReviewIndexVM review)
    {
        ViewBag.Method = methodName;
        return View(review);
    }
}

ViewComponent 默认视图

@model Cinema.DTO.ViewModels.Reviews.ReviewIndexVM
@{
    ViewData["Title"] = "Default";
}

<form asp-controller="Reviews" asp-action="@ViewBag.Method">
    <input asp-for="ReviewId" hidden />
    <input asp-for="Movie.Id" hidden />
    <input asp-for="User.Id" hidden />

    <div class="rating form-group">
        @for (int i = 10; i > 0; i--)
        {
            <input asp-for="Rating" type="radio" value="@i" id="@($"rating-star-{i}")" onclick="this.form.submit();" class="form-control rating-star"><label class="rating-star-label" for="@($"rating-star-{i}")"></label>
        }
    </div>
</form>

浏览量:

@model Cinema.DTO.ViewModels.Movies.MovieDetailsVM
@using Microsoft.AspNetCore.Identity
@using Cinema.Domain.Entities.Identity
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@{
    ViewData["Title"] = "Details";
    Layout = "~/Views/Shared/_Layout.cshtml";
    bool first = true;
    DateTime currentDate = DateTime.Now;
}

<section>
    <div class="container">
        <div class="content-wrap">
            <div class="row">
                <h1 class="h2">@Html.DisplayFor(model => model.Title)</h1>
            </div>
            <div class="row">
                <div class="col-md-4">
                    <img id="movie-poster" class="pull-left" src="~/img/movie-poster.png" />
                </div>
                <div class="col-md-8">
                    <ul class="list-unstyled movie-info">
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Title)</span>
                            @Html.DisplayFor(model => model.Title)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Year)</span>
                            @Html.DisplayFor(model => model.Year)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Actors)</span>
                            @Html.DisplayFor(model => model.Actors)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Country)</span>
                            @Html.DisplayFor(model => model.Country)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Directors)</span>
                            @Html.DisplayFor(model => model.Directors)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Duration)</span>
                            @Html.DisplayFor(model => model.Duration)
                        </li>
                        @*<li>
                                <span>@Html.DisplayNameFor(model => model.GenreMovies)</span>
                                @Html.DisplayFor(model => model.GenreMovies)
                            </li>*@
                        <li>
                            <span>@Html.DisplayNameFor(model => model.VideoLink)</span>
                            @Html.DisplayFor(model => model.VideoLink)
                        </li>
                    </ul>

                    Average rating <span class="badge">@Model.AverageRating</span>
                    <hr />

                    <div asp-authorize asp-roles="@Roles.User">

                        Your rating:

                        <div id="now-showing-details-rating-div">
                            @if (@Model.CurrentUserReview.ReviewId == 0)
                            {
                                @await Component.InvokeAsync("Review", new { methodName = "Create", review = @Model.CurrentUserReview })
                            }
                            else
                            {
                                @await Component.InvokeAsync("Review", new { methodName = "Edit", review = @Model.CurrentUserReview })
                            }
                        </div>
                    </div>

                </div>
            </div>
        </div>
       
</section>

@section Scripts {
    $(document).ready(function () {
            $('.rating-star-label').mouseover(function () {
                $('.rating-star').prop('checked', false);
            });
    });
    </script>
}

ReviewsController

[HttpGet]
[Authorize(Roles = Roles.User)]
public async Task<IActionResult> Edit(int reviewId)
{

    Review review = await _unit.Reviews.GetAsync(reviewId);

    var authorizationResult = await _authorizationService.AuthorizeAsync(User, review, OperationRequirements.Update);

    if (authorizationResult.Succeeded)
    {
        ReviewUpdateVM model = review.ToUpdateVM();
        return PartialView(model);
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }           
}

[Authorize(Roles = Roles.User)]
public async Task<IActionResult> Edit(ReviewIndexVM model)
{
    Review review = model.Create();
    var authorizationResult = await _authorizationService.AuthorizeAsync(User, review, OperationRequirements.Update);

    if (authorizationResult.Succeeded)
    {
        await _unit.Reviews.UpdateAsync(review, model.ReviewId);
        await _unit.SaveAsync();

        return ViewComponent("Review");
        //return Redirect("/Reviews/Details?reviewId=" + review.Id); 
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

这是一个工作演示:

Details.cshtml:

@model MovieDetailsVM

@{
    ViewData["Title"] = "Details";
    Layout = "~/Views/Shared/_Layout.cshtml";
    bool first = true;
    DateTime currentDate = DateTime.Now;
}

<section>
    <div class="container">
        <div class="content-wrap">
            <div class="row">
                <h1 class="h2">@Html.DisplayFor(model => model.Title)</h1>
            </div>
            <div class="row">
                <div class="col-md-8">
                    <ul class="list-unstyled movie-info">
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Title)</span>
                            @Html.DisplayFor(model => model.Title)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Year)</span>
                            @Html.DisplayFor(model => model.Year)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Actors)</span>
                            @Html.DisplayFor(model => model.Actors)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Country)</span>
                            @Html.DisplayFor(model => model.Country)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Directors)</span>
                            @Html.DisplayFor(model => model.Directors)
                        </li>
                        <li>
                            <span>@Html.DisplayNameFor(model => model.Duration)</span>
                            @Html.DisplayFor(model => model.Duration)
                        </li>
                        @*<li>
                                <span>@Html.DisplayNameFor(model => model.GenreMovies)</span>
                                @Html.DisplayFor(model => model.GenreMovies)
                            </li>*@
                        <li>
                            <span>@Html.DisplayNameFor(model => model.VideoLink)</span>
                            @Html.DisplayFor(model => model.VideoLink)
                        </li>
                    </ul>

                    Average rating <span class="badge">@Model.AverageRating</span>
                    <hr />

                    <div>

                        Your rating:

                        <div id="now-showing-details-rating-div">
                            @if (@Model.CurrentUserReview == null)
                            {
                                @await Component.InvokeAsync("Review", new { methodName = "Create", review = @Model.CurrentUserReview })
                            }
                            else
                            {
                                @await Component.InvokeAsync("Review", new { methodName = "Edit", review = @Model.CurrentUserReview })
                            }
                        </div>
                    </div>

                </div>
            </div>
        </div>
    </div>
</section>

Ajax 在 Details.cshtml:

@section Scripts {
  <script>
    $(document).ready(function () {
        $('.rating-star-label').mouseover(function () {
                $('.rating-star').prop('checked', false);
        });
    });
    function Update() {
        $.ajax({
            type: "POST",
            url: "/Reviews/Edit/@Model.CurrentUserReview.ReviewId",
            data: $("form").serialize(),
            success: function (data) {
                $("#now-showing-details-rating-div").html(data);
            }
        });
    }
  </script>
}

Components/Review/Default.cshtml:

@model ReviewIndexVM
@{
    ViewData["Title"] = "Default";
}

<form asp-controller="Reviews" asp-action="@ViewBag.Method">
    <input asp-for="ReviewId" hidden />
    <input asp-for="Movie.Id" hidden />
    <input asp-for="User.Id" hidden />

    <div class="rating form-group">
        @for (int i = 10; i > 0; i--)
        {
            <input asp-for="Rating" type="radio" value="@i" id="@($"rating-star-{i}")" onclick="Update();" class="form-control rating-star"><label class="rating-star-label" for="@($"rating-star-{i}")"></label>
        }
    </div>
</form>

控制器:

public class ReviewsController : Controller
{
    private readonly Component2_2Context _context;

    public ReviewsController(Component2_2Context context)
    {
        _context = context;
    }
    // GET: Reviews/Details/5
    public async Task<IActionResult> Details(int? id)
    {
        var reviewIndexVM = await _context.MovieDetailsVM
                                .Include(m => m.CurrentUserReview)
                                .FirstOrDefaultAsync(m => m.Id == id);
        return View(reviewIndexVM);
    }
    // GET: Reviews/Edit/5
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var reviewIndexVM = await _context.ReviewIndexVM.FindAsync(id);
        if (reviewIndexVM == null)
        {
            return NotFound();
        }
        return View(reviewIndexVM);
    }

    // POST: Reviews/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, [Bind("ReviewId,Rating")] ReviewIndexVM reviewIndexVM)
    {
        if (id != reviewIndexVM.ReviewId)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            _context.Update(reviewIndexVM);
            await _context.SaveChangesAsync();
            return ViewComponent("Review");

        }
        return new ChallengeResult();
    }

视图组件:

public class ReviewViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(string methodName, ReviewIndexVM review)
    {
        ViewBag.Method = methodName;
        return View(review);
    }
}

结果: