|
问题
如何在ASP.NET Core 2.0中使用Razor页面
解
创建一个空的项目并修改Startup.cs文件以为MVC添加服务和中间件。
publicvoid ConfigureServices(
IServiceCollection services)
{
services.AddSingleton<IMovieService, MovieService>();
services.AddMvc();
}
publicvoid Configure(
IApplicationBuilder app,
IHostingEnvironment env)
{
app.UseMvc();
}
添加一个服务和域模型(IMovieService的实现只是示例源代码中的内存列表),
publicclass Movie
{
publicint Id { get; set; }
public string Title { get; set; }
publicint ReleaseYear { get; set; }
public string Summary { get; set; }
}
publicinterface IMovieService
{
List<Movie> GetMovies();
Movie GetMovie(int id);
void AddMovie(Movie item);
void UpdateMovie(Movie item);
void DeleteMovie(int id);
bool MovieExists(int id);
}
添加输入和输出模型(通过属性绑定接收和发送数据)。
publicclass MovieInputModel
{
publicint Id { get; set; }
[Required]
public string Title { get; set; }
publicint ReleaseYear { get; set; }
public string Summary { get; set; }
}
publicclass MovieOutputModel
{
publicint Id { get; set; }
public string Title { get; set; }
publicint ReleaseYear { get; set; }
public string Summary { get; set; }
public DateTime LastReadAt { get; set; }
}
添加一个名为Pages的文件夹,并为其添加Index,_Layout,_ViewImports和_ViewStart页面。这些页面与MVC没有区别。另外,为您的CRUD页面添加一个文件夹Movies。
添加4个新的RazorPage项目到电影文件夹 - 称为索引,创建,编辑和删除。这些将添加.cshtml和.cshtml.cs文件。
这些页面中的每一个都将通过构造器注入来注入我们的 IMovieService ,例如,
private readonly IMovieService service;
public IndexModel(IMovieService service)
{
this.service = service;
}
修改Index.cshtml。
@page
@model IndexModel
@{
}
<strong>Movie Listing</strong>
<div>
<a asp-page="/Index">Home</a> |
<a asp-page="./Create">Add New</a>
</div>
<table>
<thead>
<tr>
<th>Title</th>
<th>Year</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies)
{
<tr>
<td>@item.Title</td>
<td>@item.ReleaseYear</td>
<td><a asp-page="./Edit" asp-route-id="@item.Id">Edit</a></td>
<td><a asp-page="./Delete" asp-route-id="@item.Id">Delete</a></td>
</tr>
}
</tbody>
</table>
修改Index.cshtml.cs。
public List<MovieOutputModel> Movies { get; set; }
publicvoid OnGet()
{
this.Movies = this.service.GetMovies()
.Select(item => new MovieOutputModel
{
Id = item.Id,
Title = item.Title,
ReleaseYear = item.ReleaseYear,
Summary = item.Summary,
LastReadAt = DateTime.Now
})
.ToList();
}
修改Create.cshtml。
@page
@model CreateModel
@{
}
<strong>New Movie</strong>
<form method="post">
<div asp-validation-summary="All"></div>
<label asp-for="Movie.Id"></label>
<input asp-for="Movie.Id" /><br />
<label asp-for="Movie.Title"></label>
<input asp-for="Movie.Title" />
<span asp-validation-for="Movie.Title"></span><br />
<label asp-for="Movie.ReleaseYear"></label>
<input asp-for="Movie.ReleaseYear" /><br />
<label asp-for="Movie.Summary"></label>
<textarea asp-for="Movie.Summary"></textarea><br />
<button type="submit">Save</button>
<a asp-page="./Index">Cancel</a>
</form>
修改Create.cshtml.cs。
[BindProperty]
public MovieInputModel Movie { get; set; }
publicvoid OnGet()
{
this.Movie = new MovieInputModel();
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
return Page();
var model = new Movie
{
Id = this.Movie.Id,
Title = this.Movie.Title,
ReleaseYear = this.Movie.ReleaseYear,
Summary = this.Movie.Summary
};
service.AddMovie(model);
return RedirectToPage("./Index");
}
修改Edit.cshtml。
@page "{id:int}"
@model EditModel
@{
}
<strong>Edit Movie - @Model.Movie.Title</strong>
<form method="post">
<div asp-validation-summary="All"></div>
<input type="hidden" asp-for="Movie.Id" />
<label asp-for="Movie.Title"></label>
<input asp-for="Movie.Title" />
<span asp-validation-for="Movie.Title"></span><br />
<label asp-for="Movie.ReleaseYear"></label>
<input asp-for="Movie.ReleaseYear" /><br />
<label asp-for="Movie.Summary"></label>
<textarea asp-for="Movie.Summary"></textarea><br />
<button type="submit">Save</button>
<a asp-page="./Index">Cancel</a>
</form>
修改Edit.cshtml.cs。
[BindProperty]
public MovieInputModel Movie { get; set; }
public IActionResult OnGet(int id)
{
var model = this.service.GetMovie(id);
if (model == null)
return RedirectToPage("./Index");
this.Movie = new MovieInputModel
{
Id = model.Id,
Title = model.Title,
ReleaseYear = model.ReleaseYear,
Summary = model.Summary
};
return Page();
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
return Page();
var model = new Movie
{
Id = this.Movie.Id,
Title = this.Movie.Title,
ReleaseYear = this.Movie.ReleaseYear,
Summary = this.Movie.Summary
};
service.UpdateMovie(model);
return RedirectToPage("./Index");
}
修改Delete.cshtml。
@page "{id:int}"
@model DeleteModel
@{
}
<strong>Delete Movie</strong>
<p>Are you sure you want to delete <strong>@Model.Title</strong>?</p>
<form method="post">
<input type="hidden" asp-for="Id" />
<button type="submit">Yes</button>
<a asp-page="./Index">No</a>
</form>
修改Delete.cshtml.cs。
[BindProperty]
publicint Id { get; set; }
public string Title { get; set; }
public IActionResult OnGet(int id)
{
var model = this.service.GetMovie(id);
if (model == null)
return RedirectToPage("./Index");
this.Id = model.Id;
this.Title = model.Title;
return Page();
}
public IActionResult OnPost()
{
if (!service.MovieExists(this.Id))
return RedirectToPage("./Index");
service.DeleteMovie(this.Id);
return RedirectToPage("./Index");
}
运行并浏览到/电影。
点击时,首先编辑(注意网址是/ Movies / Edit / 1),
点击删除(注意URL将是/电影/删除/ 3),
注意
你可以下载源代码来玩它。
讨论
在ASP.NET Core 2.0中引入了Razor页面,可以更快地构建简单的Web应用程序,并且是使用各种ASP.NET Core概念(如Razor,Layout Pages和Tag Helper等)的好方法。
Razor Pages使用 ASP.NET Core MVC ,但是编程模型不一样。与MVC不同,控制器,模型和视图是体系结构的不同组成部分,在Razor Pages中,这些概念被集中到一个页面模型的页面中。
页面模型
我喜欢将Page Model视为Controller和Models的组合。他们就像控制器,因为他们收到的HTTP请求,像一个模型,因为他们拥有视图的数据/属性。
对于.cshtml文件充当Page Model,它必须包含@page指令的第一行。.cshtml.cs(代码隐藏)类继承自PageModel抽象类。按照惯例,代码隐藏类有 模型 附加到页面的名称,例如索引页的代码隐藏是IndexModel。
路由
路由到页面取决于它们在您的项目目录结构中位于Pages文件夹下的位置(默认情况下)。如果URL中没有指定页面,则使用默认的索引。
在我们的示例中,我们导航到URL / Movies以查看位于我们的解决方案中的/ Pages / Movies / Index的页面。类似地,URL / Movies / Edit映射到/ Pages / Movies / Edit页面。
ASP.NET Core 2.0引入了用于生成URL的新构造,
Page()方法
asp页面标签助手
PageModel基类的RedirectToPage()方法
请注意,以/开头的URL是绝对路径并指向Pages文件夹。我们也可以使用与./或../相关的URL,或者简单的省略/。为了更好地理解,这里是从Page / Movies / Delete导航到各种URL时发生的情况,
我们可以将路由约束指定为@page指令的一部分,以指示运行时期望路由参数,或者丢失404(未找到)。在我们的编辑页面中,我们使用约束 -
如果您希望使用不同于Pages的名称作为根文件夹,则可以通过配置页面选项来实现。
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.RootDirectory = "/MyPages";
});
处理程序
如前所述,页面接收HTTP请求(即充当MVC世界中的Action),并由 处理程序方法处理。 这些处理程序返回IActionResult并使用On [verb]的约定命名。最常用的是OnGet()和OnPost()。对于异步,您可以将Async附加到名称,但是这是可选的。
PageModel基类具有RedirectToPage()方法(返回RedirectToPageResult)导航到其他页面和Page()方法(返回PageResult)返回当前页面。请注意,如果处理程序方法的返回类型为void,则运行时将返回PageResult。
为了让HTTP动词拥有多个处理器方法,我们可以使用 asp-page-handler属性来使用 命名处理器方法。这里指定的名字应该在页面类中使用约定On [动词] [处理程序]。让我们添加一个链接到我们的电影列表删除电影,
<td>
<a asp-page="./Index"
asp-page-handler="delete"
asp-route-id="@item.Id">Delete</a>
</td>
在页面模型类中添加一个方法来处理这个请求(注意它的名字和参数),
public IActionResult OnGetDelete(int id)
{
if (!service.MovieExists(id))
return RedirectToPage("./Index");
service.DeleteMovie(id);
return RedirectToPage("./Index");
}
将鼠标移到删除链接上,您会注意到像/ Movies?id = 1&handler = delete这样的URL。如果您希望用URL段替换查询参数,则将以下路由约束添加到@page指令,生成的URL将为/ Movies / delete / 1,
@page "{handler?}/{id:int?}"
捆绑
页面上的@model指令指向页面模型类,因为如前所述,该类充当Razor页面的模型。这适用于读取属性,但为了在发布数据时填充它们(即,当使用除GET之外的动词时),我们需要使用属性[BindProperty]来标记要使用模型绑定的属性。 |
|