415在ASP.NET Core 3.1 MVC中将模型传递给控制器​​操作时的状态 [英] 415 Status When Passing Model into Controller Action in ASP.NET Core 3.1 MVC

查看:81
本文介绍了415在ASP.NET Core 3.1 MVC中将模型传递给控制器​​操作时的状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经看到许多教程和文档将模型作为参数传递给控制器​​中的Action.每次执行此操作时,都会收到415状态错误(错误的媒体类型).这对我来说是个问题,因为在执行操作后我的字段会清除.许多建议在我返回View时调用模型,但这对我没有用.有谁知道这是为什么以及我该如何解决?我很沮丧,我已经尝试了很多事情,但它永远都行不通:(

I've seen many tutorials and documentation pass the model as an argument in an Action in a controller. Every time I do this I get a 415 status error (incorrect media type) when the action is called. This is problematic for me because my fields clear after the action occurs. Many have suggested calling the model when I return the View, but that has not been working for me. Does anyone know why that is and how I can fix it? I'm so frustrated I've tried so many things and it just never works :(

我想如何将模型作为参数传递的示例:

Example of how I want to pass the model as an argument:

[HttpGet("[action]")]
public async Task<IActionResult> Search(Movies model, int ID, string titleSearch, 
    string genreSearch)
{

    return View(model);
}

我的观点:

@model IEnumerable<MyApp.Models.Movies>

@{ 
    ViewData["Title"] = "Movies";
}

<form method="get" role="form" asp-controller="MoviesList" asp-action="Index">
    <label>Movie Genre</label>
    <select name="movieGenre" asp-items="@(new SelectList(ViewBag.genre, "ID", "Genre"))"></select>

    <label>Movie Title</label>
    <input type="search" value="@ViewData["movieTitle"]" name="movieTitle" />

    <input type="submit" value="Search" asp-controller="MoviesList" asp-action="Search" />
</form>

<input type="hidden" name="ID" value="@ViewBag.pageID"

<table>
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(m => m.Title)
            </th>
            <th>
                @Html.DisplayNameFor(m => m.Genre)
            </th>
        </tr>
    </thead>
    <tbody>
        @foreach(var item in Model)
        {
            <tr>
                <th>
                    @Html.DisplayFor(modelItem => item.Title)
                </th>
                <th>
                    @Html.DisplayFor(modelItem => item.Genre)
                </th>
            </tr>
        }
    </tbody>
</table>

我的控制器:

//This action is called when the page is first called
[HttpGet("[action]")]
[Route("/MoviesList/Index/id")]
public async Task<IActionResult> Index(int id)
{
    //using ViewBag to set the incoming ID and save it in the View
    //so that I can access it from my search action
    ViewBag.pageID = id;
    //calling a query to load data into the table in the View
    //var query = query

    return View(await query);
}

//searching the movies list with this action
[HttpGet("[action]")]
public async Task<IActionResult> Search(int ID, string titleSearch, string genreSearch)
{
    int id = ID;
    ViewData["titleSearch"] = titleSearch;

    //do some necessary conversions to the incoming data (the dropdowns for example come in as 
    //integers that match their value in the DB

    var query = from x in _db.Movies
                .Where(x => x.Id == id)
                select x;

    //some conditionals that check for null values
    //run the search query
    query = query.Where(x =>
    x.Title.Contains(titleSearch) &&
    x.Genre.Contains(genreSearch));

    //when this return happens, I do get all of my results from the search,
    //but then all of the fields reset & my hidden ID also resets
    //this is problematic if the user decides they want to search again with 
    //different entries
    return View("Index", await query.AsNoTracking().ToListAsync());
}

总体而言,我的目标是在我的操作完成后不清除任何字段,并允许用户使用新条目来重新调用该操作.以我的理解,将模型作为参数可以帮助我实现目标,但是我没有运气.请让我知道我如何实现这个目标.谢谢您的时间!

Overall, my goal is to not have any of the fields clear after my action is complete, and allow the user to re-call the action with new entries. From my understanding, passing the model as an argument can help me achieve my goal, but I haven't had any luck. Please let me know how I can achieve this goal. Thank you for your time!

推荐答案

您的代码有很多错误.我不确定从哪里开始,但会尽力列出一些:

There are so many things wrong in your code. I am not sure where to start but will try my best to list out a few:

  1. [HttpGet]
  2. 的使用
  3. 属性路由的使用, [Route]
  4. 表格发布
  5. 过度使用 ViewBag


1.使用 [HttpGet]

我不想说您使用 [HttpGet] 作为参数传递名称的方式是错误的,但是您的设置将始终忽略控制器名称!


1. Use of [HttpGet]

I don't want to say the way you used [HttpGet] passing a name as the parameter is wrong, but your setup will always ignore the controller name!

您传入的 [action] 是呼叫令牌替换,它将被替换为动作名称的值,因此:

The [action] you passed in is call token replacement, which will be replaced with the value of the action name so:

/*
 * [HttpGet("[action]")] on Search action  =>  [HttpGet("search")]  =>  matches /search
 * [HttpGet("[action]")] on Index action   =>  [HttpGet("index")]   =>  matches /index
 */

看看那是怎么错!您缺少控制器名称!

See how wrong that is! You're missing the controller name!

请求/moviesList/index 不会从MoviesList控制器调用Index方法,但是请求/index 将会!

A request /moviesList/index will not call the Index method from the MoviesList controller, but a request /index will!

只需取出模板/令牌替换参数.并且默认情况下,如果您不使用任何HTTP动词模板(即 [HttpGet] )标记控制器操作,则默认情况下它们会处理HTTP GET请求.

Just take out the template/token replacement parameter. And by default, if you don't mark the controller action with any HTTP verb templates, i.e., [HttpGet], they're default to handle HTTP GET requests.

我不想说在Model-View-Controller应用程序中使用属性路由是错误的,但是在构建RESTful API应用程序时主要使用属性路由.

I don't want to say using attribute routing in a Model-View-Controller application is wrong, but attribute routing is used mostly when you're building a RESTful API application.

默认情况下,应用被设置为使用常规路由,当您首次创建应用时,该路由应随模板一起提供:

By default, the app is setup to use the conventional routing, which should come with the template when you first create your application:

namespace DL.SO.SearchForm.WebUI
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            ...
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            ...

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });            
        }
    }
}

您使用 [Route] 属性的方式给我的印象是您不知道它们是什么,或者至少您感到困惑.使用常规路由,即使您没有在控制器上放置 [Route] ,以下请求也应通过默认"命令到达其相应的控制器动作.路由:

The way you used [Route] attribute gives me an impression that you don't know what they're or at least you are confused. With the conventional routing, even if you don't put [Route] on the controllers, the following requests should arrive to their corresponding controller actions by the "default" routing:

/*
 * /moviesList/index     GET    =>    MoviesList controller, Index action
 * /moviesList/search    GET    =>    MoviesList controller, Search action
 */

顺便说一句,名为 MoviesListController 的控制器很糟糕.我将其称为 MovieController .

By the way, a controller named MoviesListController is awful. I will just call it MovieController.

在表单中,您无法在提交按钮上指定控制器和操作.无论如何,它不是锚标签.

Within the form, you can't specify a controller and the action on the submit button. It's not an anchor tag anyway.

< input type ="hidden"name ="ID"value ="@ ViewBag.pageID" 在表单之外.表单将如何知道这是什么,然后将正确的值发回去?

And <input type="hidden" name="ID" value="@ViewBag.pageID" is outside the form. How would the form know what that is and post the correct value back?

从技术上讲,您只能使用 ViewBag 在控制器和视图之间传输数据. ViewData 仅在当前请求中有效,并且您只能将数据从控制器传输到视图,反之亦然.

Technically you can only use ViewBag to transfer data between controller to view. ViewData is only valid in the current request, and you can only transfer data from controller to view, not vice-versa.

此外,它们是所谓的弱类型集合.它们旨在将少量数据传入和传出控制器和视图,例如页面标题.如果您过度使用它们,您的应用程序将变得难以维护,因为您必须记住使用时数据的类型.

In additional, they're so-called weakly typed collections. They're designed to transfer small amount of data in and out of controllers and views, like the page title. If you overuse them, your applications will become so hard to maintain as you have to remember what type the data is when using it.

通过过度使用 ViewBag/ViewData ,您基本上就删除了有关C#和amp;的最佳功能之一.剃刀-强力打字.

By overusing ViewBag / ViewData, you're basically removing one of the best features about C# & Razor - strongly typed.

最好的方法是在视图中指定视图模型.您可以通过控制器动作将视图模型的实例传递给视图.视图模型仅定义视图所需的数据!您不应将整个数据库模型传递给视图,以便用户可以使用您的其他重要信息!

The best approach is to specify a view model in the view. You pass an instance of the view model to the view from the controller action. The view model defines only the data the view needs! You should not pass your entire database model to the view so that users can use your other important information!

我不想使用一种方法来列出所有电影以及搜索过滤器,而是将它们分开.搜索表单将使用 [HttpPost] 而不是 [HttpGet] .

Instead of using a single method to handle listing all the movies as well as the search filters, I would like to separate them. The search form will be using [HttpPost] instead of [HttpGet].

这样,我只需要回发搜索过滤器数据,现在就可以在Index操作上定义自定义参数,并将Post操作重定向到Index操作.

That way I will only need to post back the search filters data, and I can now define custom parameters on the Index action and have the Post action redirect to the Index action.

我会告诉你我的意思.

首先,我将定义视图所需的所有视图模型:

First I will define all the view models I need for the view:

namespace DL.SO.SearchForm.WebUI.Models.Movie
{
    // This view model represents each summarized movie in the list.
    public class MovieSummaryViewModel
    {
        public int MovieId { get; set; }

        public string MovieTitle { get; set; }

        public string MovieGenre { get; set; }

        public int MovieGenreId { get; set; }
    }

    // This view model represents the data the search form needs
    public class MovieListSearchViewModel
    {
        [Display(Name = "Search Title")]
        public string TitleSearchQuery { get; set; }

        [Display(Name = "Search Genre")]
        public int? GenreSearchId { get; set; }

        public IDictionary<int, string> AvailableGenres { get; set; }
    }

    // This view model represents all the data the Index view needs
    public class MovieListViewModel
    {
        public MovieListSearchViewModel Search { get; set; }

        public IEnumerable<MovieSummaryViewModel> Movies { get; set; }
    }
}

控制器

接下来,控制器来了:

The Controller

Next, here comes the controller:

这里要注意的一件事是,您必须使用在视图模型中定义POST动作参数的方式来命名POST操作参数,就像 MovieListSearchViewModel search 一样.

One thing to pay attention here is that you have to name the POST action parameter the same way as you define it in the view model, like so MovieListSearchViewModel search.

您不能使用其他名称来命名参数,因为我们会将部分视图模型发布回MVC,并且默认情况下,模型绑定仅在与名称匹配时才为您绑定数据.

You can't name the parameter name something else because we're posting partial view model back to MVC, and by default, the model binding will only bind the data for you if it matches the name.

namespace DL.SO.SearchForm.WebUI.Controllers
{
    public class MovieController : Controller
    {
        // See here I can define custom parameter names like t for title search query,
        // g for searched genre Id, etc
        public IActionResult Index(string t = null, int? g = null)
        {
            var vm = new MovieListViewModel
            {
                Search = new MovieListSearchViewModel
                {
                    // You're passing whatever from the query parameters
                    // back to this search view model so that the search form would
                    // reflect what the user searched!
                    TitleSearchQuery = t,
                    GenreSearchId = g,

                    // You fetch the available genres from your data sources, although
                    // I'm faking it here.
                    // You can use AJAX to further reduce the performance hit here
                    // since you're getting the genre list every single time.
                    AvailableGenres = GetAvailableGenres()
                },

                // You fetch the movie list from your data sources, although I'm faking
                // it here.
                Movies = GetMovies()
            };

            // Filters
            if (!string.IsNullOrEmpty(t))
            {
                // Filter by movie title
                vm.Movies = vm.Movies
                    .Where(x => x.MovieTitle.Contains(t, StringComparison.OrdinalIgnoreCase));
            }

            if (g.HasValue)
            {
                // Filter by movie genre Id
                vm.Movies = vm.Movies
                    .Where(x => x.MovieGenreId == g.Value);
            }

            return View(vm);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        // You have to name the paramter "Search" as you named so in its parent
        // view model MovieListViewModel
        public IActionResult Search(MovieListSearchViewModel search)
        {
            // This is the Post method from the form.
            // See how I just put the search data from the form to the Index method.
            return RedirectToAction(nameof(Index), 
                new { t = search.TitleSearchQuery, g = search.GenreSearchId });
        }

        #region Methods to get fake data

        private IEnumerable<MovieSummaryViewModel> GetMovies()
        {
            return new List<MovieSummaryViewModel>
            {
                new MovieSummaryViewModel
                {
                    MovieId = 1,
                    MovieGenreId = 1,
                    MovieGenre = "Action",
                    MovieTitle = "Hero"
                },
                new MovieSummaryViewModel
                {
                    MovieId = 2,
                    MovieGenreId = 2,
                    MovieGenre = "Adventure",
                    MovieTitle = "Raiders of the Lost Ark (1981)"
                },
                new MovieSummaryViewModel
                {
                    MovieId = 3,
                    MovieGenreId = 4,
                    MovieGenre = "Crime",
                    MovieTitle = "Heat (1995)"
                },
                new MovieSummaryViewModel
                {
                    MovieId = 4,
                    MovieGenreId = 4,
                    MovieGenre = "Crime",
                    MovieTitle = "The Score (2001)"
                }
            };
        }

        private IDictionary<int, string> GetAvailableGenres()
        {
            return new Dictionary<int, string>
            {
                { 1, "Action" },
                { 2, "Adventure" },
                { 3, "Comedy" },
                { 4, "Crime" },
                { 5, "Drama" },
                { 6, "Fantasy" },
                { 7, "Historical" },
                { 8, "Fiction" }
            };
        }

        #endregion
    }
}

景观

最后是视图:

@model DL.SO.SearchForm.WebUI.Models.Movie.MovieListViewModel
@{ 
    ViewData["Title"] = "Movie List";

    var genreDropdownItems = new SelectList(Model.Search.AvailableGenres, "Key", "Value");
}

<h2>Movie List</h2>
<p class="text-muted">Manage all your movies</p>
<div class="row">
    <div class="col-md-4">
        <div class="card">
            <div class="card-body">
                <form method="post" asp-area="" asp-controller="movie" asp-action="search">
                    <div class="form-group">
                        <label asp-for="Search.GenreSearchId"></label>
                        <select asp-for="Search.GenreSearchId"
                                asp-items="@genreDropdownItems"
                                class="form-control">
                            <option value="">- select -</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label asp-for="Search.TitleSearchQuery"></label>
                        <input asp-for="Search.TitleSearchQuery" class="form-control" />
                    </div>
                    <button type="submit" class="btn btn-success">Search</button>
                </form>
            </div>
        </div>
    </div>
    <div class="col-md-8">
        <div class="table-responsive">
            <table class="table table-hover">
                <thead>
                    <tr>
                        <th>#</th>
                        <th>Title</th>
                        <th>Genre</th>
                    </tr>
                </thead>
                <tbody>
                    @if (Model.Movies.Any())
                    {
                        foreach (var movie in Model.Movies)
                        {
                            <tr>
                                <td>@movie.MovieId</td>
                                <td>@movie.MovieTitle</td>
                                <td>@movie.MovieGenre</td>
                            </tr>
                        }
                    }
                    else
                    {
                        <tr>
                            <td colspan="3">No movie matched the searching citiria!</td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>
    </div>
</div>


屏幕截图

首次进入电影"页面时:


Screenshots

When you first land on the Movies page:

正确显示了可用的流派列表和电影列表:

The available Genre list as well as the movie list is shown correctly:

按类型搜索:

按标题搜索:

这篇关于415在ASP.NET Core 3.1 MVC中将模型传递给控制器​​操作时的状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆