[英]How to create a custom response for HTTP status 415 in ASP.NET Core 3.1?
[英]415 Status When Passing Model into Controller Action in ASP.NET Core 3.1 MVC
我已經看到許多教程和文檔通過 model 作為 controller 中的操作中的參數。 每次執行此操作時,我都會在調用操作時收到 415 狀態錯誤(不正確的媒體類型)。 這對我來說是有問題的,因為在操作發生后我的字段會清除。 許多人建議在我返回視圖時調用 model,但這對我不起作用。 有誰知道這是為什么以及我該如何解決? 我很沮喪,我嘗試了很多東西,但它從來沒有奏效:(
我想如何將 model 作為參數傳遞的示例:
[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>
我的 Controller:
//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());
}
總的來說,我的目標是在我的操作完成后不清除任何字段,並允許用戶使用新條目重新調用操作。 據我了解,將 model 作為參數傳遞可以幫助我實現目標,但我沒有任何運氣。 請讓我知道如何實現這一目標。 感謝您的時間!
您的代碼中有很多錯誤。 我不知道從哪里開始,但會盡力列出一些:
[HttpGet]
的使用[Route]
ViewBag
[HttpGet]
的使用我不想說您使用[HttpGet]
傳遞名稱作為參數的方式是錯誤的,但是您的設置將始終忽略 controller 名稱!
您傳入的[action]
是調用令牌替換,它將被替換為操作名稱的值,因此:
/*
* [HttpGet("[action]")] on Search action => [HttpGet("search")] => matches /search
* [HttpGet("[action]")] on Index action => [HttpGet("index")] => matches /index
*/
看看這是多么錯誤! 您缺少 controller 名稱!
請求/moviesList/index
不會調用 MoviesList controller 中的 Index 方法,但請求/index
會!
只需取出模板/令牌替換參數即可。 默認情況下,如果您不使用任何 HTTP 動詞模板(即[HttpGet]
)標記 controller 操作,則它們默認處理 HTTP 請求。
[Route]
我不想說在 Model-View-Controller 應用程序中使用屬性路由是錯誤的,但屬性路由主要用於構建 RESTful API 應用程序時。
默認情況下,該應用程序設置為使用傳統路由,當您首次創建應用程序時,模板應隨附該路由:
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]
放在控制器上,以下請求也應通過“默認”路由到達其相應的 controller 操作:
/*
* /moviesList/index GET => MoviesList controller, Index action
* /moviesList/search GET => MoviesList controller, Search action
*/
順便說一句,名為MoviesListController
的 controller 很糟糕。 我將稱之為MovieController
。
在表單中,您不能指定 controller 和提交按鈕上的操作。 無論如何,它不是錨標簽。
並且<input type="hidden" name="ID" value="@ViewBag.pageID"
在表單之外。 表單如何知道那是什么並返回正確的值?
ViewBag / ViewData
的過度使用從技術上講,您只能使用ViewBag
在 controller 之間傳輸數據以查看。 ViewData
只在當前請求中有效,只能從controller傳輸數據到view,反之不行。
此外,它們是所謂的弱類型 collections。 它們旨在將少量數據傳入和傳出控制器和視圖,例如頁面標題。 如果您過度使用它們,您的應用程序將變得非常難以維護,因為您必須記住使用數據時的數據類型。
通過過度使用ViewBag / ViewData
,您基本上刪除了關於 C# 和 Razor 的最佳功能之一 - 強類型。
最好的方法是在視圖中指定一個視圖 model。 您將視圖 model 的實例傳遞給 controller 操作的視圖。 視圖 model 只定義了視圖需要的數據! 您不應將整個數據庫 model 傳遞給視圖,以便用戶可以使用您的其他重要信息!
我不想使用單一方法來處理列出所有電影以及搜索過濾器,而是想將它們分開。 搜索表單將使用[HttpPost]
而不是[HttpGet]
。
這樣我只需要回發搜索過濾器數據,我現在可以在 Index 操作上定義自定義參數,並將 Post 操作重定向到 Index 操作。
我會告訴你我的意思。
首先,我將定義視圖所需的所有視圖模型:
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; }
}
}
接下來是controller:
這里要注意的一件事是,您必須以與在視圖 model 中定義它的方式相同的方式命名 POST 操作參數,例如MovieListSearchViewModel search
。
您不能將參數名稱命名為其他名稱,因為我們將部分視圖 model 發布回 MVC,默認情況下,model 綁定只會在與名稱匹配時為您綁定數據。
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>
當您第一次登陸電影頁面時:
可用的流派列表以及電影列表正確顯示:
按類型搜索:
按標題搜索:
您並沒有真正向 controller 操作“傳遞參數” - 您正在向應用程序定義的端點發出 HTTP 請求,在您的應用程序中運行的各種中間件試圖處理這些請求。 在這種情況下,這些中間件之一是 MVC 框架/模塊,它嘗試將 map 路由值(控制器、操作等)到匹配的類,並在相關的地方查詢字符串或表單值。
由於您已將 Search 操作定義為僅匹配 GET 請求,因此您正在讀取查詢字符串(您通常在導航欄中看到的?foo=bar&bar=baz
內容)。 A C# class 不是您可以作為查詢字符串值發送的東西(有一些方法可以解決這個問題,使用屬性,但這對於您的示例來說有點矯枉過正)。 如果你還沒有讀過,我會讀https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1 。
上一個示例中的 Search 操作將起作用,但您已將輸入呈現在<form>
元素之外; 要包含它,您需要在表單中呈現它或使用form="form id here"
屬性將其與該表單相關聯(您需要在表單中添加一個id="something"
屬性也可以工作)。
<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" />
<input type="hidden" name="ID" value="@ViewBag.pageID" />
</form>
如果您想保留用於提交搜索表單的值,您有兩個選擇(嗯,更實際,但現在說兩個):
我個人將 go 與 #2 一起使用,因為它還可以使您的視圖更清晰地綁定。 所以:
public class SearchViewModel
{
public SearchViewModel()
{
Matches = Array.Empty<Movies>();
Genres = Array.Empty<Genre>();
}
public int? ID { get; set; }
public string Title { get; set; }
public string Genre { get; set; }
public IEnumerable<Movies> Matches { get; set; }
public IEnumerable<Genre> Genres { get; set; }
}
看法:
@model SearchViewModel
@{
ViewData["Title"] = "Movies";
}
<form method="get" role="form" asp-controller="MoviesList" asp-action="Index">
<label>Movie Genre</label>
<select asp-for="Genre" asp-items="@(new SelectList(Model.Genres, "ID", "Genre"))"></select>
<label>Movie Title</label>
<input type="search" asp-for="Title" />
<button>Search</button>
<input type="hidden" asp-for="ID" />
</form>
<table>
<thead>
<tr>
<th>
Title
</th>
<th>
Genre
</th>
</tr>
</thead>
<tbody>
@foreach(var item in Model.Matches)
{
<tr>
<td>
@item.Title
</td>
<td>
@item.Genre
</td>
</tr>
}
</tbody>
</table>
Controller
如果你讓你的動作參數可以為空,你實際上只需要一個動作用於“默認”動作和搜索:
[HttpGet("[action]")]
[Route("/MoviesList/Index/id")]
public async Task<IActionResult> Index(int? id, string title = null, string genre = null)
{
var model = new SearchViewModel();
// ... add code for populating model.Genres...
var query = _db.Movies.AsQueryable();
if(id != null)
{
model.ID = id.value;
query = query.Where(m => m.ID == id);
}
if(title != null)
{
model.Title = title;
query = query.Where(m => m.Title.Contains(title));
}
if(genre != null)
{
model.Genre = genre;
query = query.Where(m => m.Genre.Contains(Genre));
}
model.Matches = await query
.OrderBy(m => m.Title)
.ToListAsync();
return View(model);
}
這是完全未經測試的,所以請謹慎購買。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.