創(chuàng)建ASP.NET Core MVC應(yīng)用程序(4)-添加CRUD動(dòng)作方法和視圖

創(chuàng)建ASP.NET Core MVC應(yīng)用程序(4)-添加CRUD動(dòng)作方法和視圖

創(chuàng)建CRUD動(dòng)作方法及視圖

參照VS自帶的基架(Scaffold)系統(tǒng)-MVC Controller with views, using Entity Framework我們來創(chuàng)建CRUD方法。

① 將上一篇的Models/UserContext.cs文件中的用來指定使用的數(shù)據(jù)庫邏輯的OnConfiguring方法?刪除,將邏輯移到Startup.cs文件中的ConfigureServices方法中。

public void ConfigureServices(IServiceCollection services)
{
    string connectionString = Configuration.GetConnectionString("MyConnection");

    services.AddDbContext<UserContext>(options =>
                options.UseMySQL(connectionString));

    // Add framework services.
    services.AddMvc();
}

② 在UserController.cs 構(gòu)造函數(shù)中采用依賴注入來注入一個(gè)數(shù)據(jù)庫上下文到該控制器。數(shù)據(jù)庫上下文將被應(yīng)用到控制器中的每一個(gè)CRUD方法。

private readonly UserContext _context;

public UserController(UserContext context)
{
    _context = context;
}

③ 在UserController.cs中?添加基本的CRUD方法:

// GET: /<controller>/
public async Task<IActionResult> Index()
{
    return View(await _context.Users.ToListAsync());
}

// GET: User/Details/1
public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);

    if (user == null)
    {
        return NotFound();
    }

    return View(user);
}

// GET: User/Create
public IActionResult Create()
{
    return View();
}

// POST: User/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Name,Email,Bio")]User user)
{
    if (ModelState.IsValid)
    {
        _context.Add(user);
        await _context.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    return View(user);
}

//GET: User/Edit/1
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
    if (user == null)
    {
        return NotFound();
    }
    return View(user);
}

// POST: User/Edit/1
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Name,Email,Bio")]User user)
{
    if (id != user.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(user);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!UserExists(user.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }

    return View(user);
}

//// GET: User/Delete/5
public async Task<IActionResult> Delete(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
    if (user == null)
    {
        return NotFound();
    }
    return View(user);
}

// POST: User/Delete/1
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
    _context.Users.Remove(user);
    await _context.SaveChangesAsync();
    return RedirectToAction("Index");
}

private bool UserExists(int id)
{
    return _context.Users.Any(e => e.ID == id);
}

一個(gè)http://localhost:5000/User 這樣的請(qǐng)求到達(dá)User控制器后,將會(huì)從User表返回所有的數(shù)據(jù),將將這些數(shù)據(jù)傳遞到Index視圖:

④ 在Views/User文件夾中添加與上述Action方法名稱相對(duì)應(yīng)的Index.cshtml文件、Create.cshtml文件、Details.cshtml文件、Edit.cshtml文件、Delete.cshtml文件。

Create.cshtml運(yùn)行效果:

Details.cshtml運(yùn)行效果:

Edit.cshtml運(yùn)行效果:

Delete.cshtml運(yùn)行效果:

強(qiáng)類型模型和@model關(guān)鍵字

MVC提供了傳遞強(qiáng)類型對(duì)象給視圖的能力,這樣為你的代碼提供了更好的編譯時(shí)檢查,并在VS中提供了更豐富的智能感知功能。

查看UserController/Details方法:

// GET: User/Details/1
public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var user = await _context.Users.SingleOrDefaultAsync(u => u.ID == id);

    if (user == null)
    {
        return NotFound();
    }

    return View(user);
}

id參數(shù)通常作為路由數(shù)據(jù)來傳遞,比如 http://localhost:5000/user/details/1 會(huì):

  • Controller設(shè)置為user(第一個(gè)URL段)
  • Action設(shè)置為details(第二個(gè)URL段)
  • id設(shè)置為1(第三個(gè)URL段)

你也可以通過查詢字符串來傳遞id:
http://localhost:5000/user/details?id=1

如果指定的User被找到,則User Model實(shí)例將被傳遞到Details視圖:

return View(user);

查看Views/User/Details.cshtml文件:

@model IEnumerable<MyFirstApp.Models.User>

@{
    ViewData["Title"] = "Index - User List";
}

<h2>Index - User List</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Email)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Bio)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Email)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Bio)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
        }
    </tbody>
</table>

你會(huì)發(fā)現(xiàn)在頂部有一個(gè)@model語句,你可以指定視圖所期望的對(duì)象類型。

@model MyFirstApp.Models.User

@model指令允許你通過使用強(qiáng)類型的Model對(duì)象來訪問從控制器傳遞到視圖的User對(duì)象。例如,在Details.cshtml視圖中,通過使用強(qiáng)類型的Model對(duì)象傳遞User的每一個(gè)字段到DisplayNameForDisplayFor HTML Helper。

再來查看Index.cshtml文件和User控制器中的Index方法。注意在調(diào)用View方法時(shí),是如何創(chuàng)建一個(gè)List對(duì)象的。下面的代碼將從Index Action方法傳遞整個(gè)User到視圖中。

User控制器中的Index方法:

public async Task<IActionResult> Index()
{
    return View(await _context.Users.ToListAsync());
}

Index.cshtml文件最頂部:

@model IEnumerable<MyFirstApp.Models.User>

@model指令允許你訪問通過強(qiáng)類型的Model從控制器傳遞到視圖的User列表。例如,在Index.cshtml視圖中,在強(qiáng)類型的Model對(duì)象上通過foreach語句遍歷了整個(gè)User列表:

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Bio)
        </td>
        <td>
            <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
            <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
            <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
        </td>
    </tr>
}

添加倉儲(chǔ)類

首先,新建一個(gè)Repositories文件夾。在該文件夾下定義一個(gè)IUserRepository接口。

namespace MyFirstApp.Repositories
{
    public interface IUserRepository
    {
        Task<IEnumerable<User>> GetAll();
        Task<User> Get(int id);
        void Add(User user);
        void Update(User user);
        void Delete(int id);
        bool UserExists(int id);
    }
}

接著再添加一個(gè)UserRepository來實(shí)現(xiàn)IUserRepository接口。將之前定義的UserContext.cs邏輯移到該類中,在UserRepository.cs 構(gòu)造函數(shù)中采用依賴注入來注入一個(gè)數(shù)據(jù)庫上下文(UserContext)到該倉儲(chǔ)類。數(shù)據(jù)庫上下文將被應(yīng)用到倉儲(chǔ)類中的每一個(gè)CRUD方法。

public class UserRepository : IUserRepository
{
    private readonly UserContext _context;

    public UserRepository(UserContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<User>> GetAll()
    {
        return await _context.Users.ToListAsync();
    }

    public async Task<User> Get(int id)
    {
        return await _context.Users.SingleOrDefaultAsync(u => u.ID == id);
    }

    public async void Add(User user)
    {
        //_context.Users.Add(user);
        _context.Add(user);
        await _context.SaveChangesAsync();
    }

    public async void Update(User user)
    {
        //_context.Users.Update(user);
        _context.Update(user);
        await _context.SaveChangesAsync();
    }

    public async void Delete(int id)
    {
        var user =   _context.Users.SingleOrDefault(u => u.ID == id);
        _context.Users.Remove(user);
        await _context.SaveChangesAsync();
    }

    public bool UserExists(int id)
    {
        return _context.Users.Any(e => e.ID == id);
    }
}

在Controller構(gòu)造函數(shù)中依賴注入U(xiǎn)serRepository

再修改Controllers/UserController.cs文件,將private readonlyUserContext變量刪除:

private readonly UserContext _context;

添加IUserRepository變量:

private readonly IUserRepository _userRepository;
public UserController(IUserRepository userRepository)
{
    _userRepository = userRepository;
}

將所有方法中的_context操作刪除,替換成_userRepository。例如,將Index方法中的_context.Users.ToListAsync()刪除:

return View(await _context.Users.ToListAsync());

替換成

return View(await _context.Users.ToListAsync());

最終的UserController.cs如下:

public class UserController : Controller
{
    private readonly IUserRepository _userRepository;

    public UserController(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    // GET: /<controller>/
    public async Task<IActionResult> Index()
    {
        return View(await _userRepository.GetAll());
    }

    // GET: User/Details/1
    public async Task<IActionResult> Details(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var user = await _userRepository.Get(id.Value);

        if (user == null)
        {
            return NotFound();
        }

        return View(user);
    }

    // GET: User/Create
    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Create([Bind("ID,Name,Email,Bio")]User user)
    {
        if (ModelState.IsValid)
        {
            _userRepository.Add(user);
            return RedirectToAction("Index");
        }
        return View(user);
    }

    //GET: User/Edit/1
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var user = await _userRepository.Get(id.Value);
        if (user == null)
        {
            return NotFound();
        }
        return View(user);
    }

    // POST: User/Edit/1
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Edit(int id, [Bind("ID,Name,Email,Bio")]User user)
    {
        if (id != user.ID)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            try
            {
                _userRepository.Update(user);
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!_userRepository.UserExists(user.ID))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
            return RedirectToAction("Index");
        }

        return View(user);
    }

    //// GET: User/Delete/5
    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var user = await _userRepository.Get(id.Value);
        if (user == null)
        {
            return NotFound();
        }
        return View(user);
    }

    // POST: User/Delete/1
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public IActionResult DeleteConfirmed(int id)
    {
            _userRepository.Delete(id);
        return RedirectToAction("Index");
    }
}

注冊(cè)倉儲(chǔ)

通過定義Repository接口,從MVC Controller中解耦該repository類。通過注入一個(gè)UserRepository來代替直接在Controller里面實(shí)例化一個(gè)UserRepository類。

為了注入一個(gè)Repository到Controller,我們必須通過DI容器來注冊(cè)它,打開Startup.cs?文件,在ConfigureServices方法添加如下代碼:

// Add our repository type
services.AddScoped<IUserRepository, UserRepository>();

DataAnnotations & Tag Helpers

我們?yōu)?em>Models/User.cs文件添加DisplayDataType注解,首先要添加必要的命名空間using System.ComponentModel.DataAnnotations;

再將屬性在視圖上顯示成中文:

Display Attribute指定字段的顯示名,DataTypeAttribute指定數(shù)據(jù)類型。

最終的顯示效果如下:

打開Views/User/Index.cshtml,你會(huì)發(fā)現(xiàn)Edit,Details,Delete鏈接是由MVC Core Anchor Tag Helper生成的。

<td>
    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>

Tag Helpers允許服務(wù)器代碼在Razor文件中參與創(chuàng)建和渲染HTML元素。在上述代碼中,AnchorTagHelper從Controller Action動(dòng)作方法和路由ID動(dòng)態(tài)生成HTMLhref屬性值。

查看Startup.cs中的Configure方法:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

ASP.NET Core會(huì)將http://localhost:5000/User/Edit/4 轉(zhuǎn)換成發(fā)送給User控制器的Edit方法(帶有值為4的Id參數(shù))的請(qǐng)求。

查看UserController.cs中的[HttpPost]版本的Edit方法:

// POST: User/Edit/1
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, [Bind("ID,Name,Email,Bio")]User user)
{
    if (id != user.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            // _context.Update(user);
            // await _context.SaveChangesAsync();
            _userRepository.Update(user);
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!_userRepository.UserExists(user.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }

    return View(user);
}

[Bind] Attribute是一種防止over-posting(過度提交)的方式。應(yīng)該只把你需要改變的屬性包含到[Bind] Attribute中。

[ValidateAntiForgeryToken] Attribute是用來防止偽造請(qǐng)求的,會(huì)與Views/User/Edit.cshtml視圖文件生成的反偽造標(biāo)記(Token)進(jìn)行配對(duì)。Views/User/Edit.cshtml視圖文件通過Form Tag Helper來生成反偽造標(biāo)記(Token)。

<form asp-action="Edit">

Form Tag Helper生成一個(gè)隱藏的防偽標(biāo)記必須和User控制器中的Eidt方法的[ValidateAntiForgeryToken]產(chǎn)生的防偽標(biāo)記相匹配。

查看Edit.cshtml,會(huì)發(fā)現(xiàn)基架系統(tǒng)(Scaffolding System)會(huì)為User類的每一個(gè)屬性生成用來呈現(xiàn)的<label><input>元素。

<form asp-action="Edit">
    <div class="form-group">
        <label asp-for="Email" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="Email" class="form-control" />
            <span asp-validation-for="Email" class="text-danger" />
        </div>
    </div>
</form>

基架代碼使用了多個(gè)Tag Helper方法來簡(jiǎn)化HTML標(biāo)記。

  • Label Tag Helper用來顯示字段的名字。
  • Input Tag Helper用來呈現(xiàn)HTML<input>元素。
  • Validation Tag Helper用來顯示關(guān)聯(lián)屬性的驗(yàn)證信息。

最終在瀏覽器中為<form>元素所生成的HTML如下:

HTML<form>中的actionAttribute設(shè)置成POST到/User/Edit/idURL(所有<input>元素都在該<form>元素中)。當(dāng)點(diǎn)擊Save按鈕時(shí),表單數(shù)據(jù)會(huì)被發(fā)送(POST)到服務(wù)器。在</form>元素的上面顯示了Form Tag Helper所生成的隱藏的XSRF反偽造標(biāo)記。

處理POST請(qǐng)求

查看[HttpPost]版本的Edit方法:

[ValidateAntiForgeryToken]驗(yàn)證Form Tag Helper中的反偽造標(biāo)記生成器所生成的隱藏的XSRF反偽造標(biāo)記。

模型綁定(Model Binding)機(jī)制接受POST過來的表單數(shù)據(jù)并創(chuàng)建一個(gè)User對(duì)象并作為user參數(shù)。ModelState.IsValid方法驗(yàn)證從表單提交過來的數(shù)據(jù)可以用來修改一個(gè)User對(duì)象。如果數(shù)據(jù)有效,就可以進(jìn)行保存。被更新的數(shù)據(jù)通過調(diào)用數(shù)據(jù)庫的上下文(Database Context)的SaveChangesAsync方法來保存到數(shù)據(jù)庫中。數(shù)據(jù)保存之后,代碼將用戶重定向到UserController類的Index方法。該頁面會(huì)顯示剛剛被改動(dòng)后的最新的用戶集合。

在表單被POST到服務(wù)器之前,客戶端驗(yàn)證會(huì)檢查所有字段上的驗(yàn)證規(guī)則,如果有任何驗(yàn)證錯(cuò)誤,則會(huì)顯示該錯(cuò)誤信息,并且表單不會(huì)被發(fā)送到服務(wù)器。如果禁用了JS,將不會(huì)有客戶端驗(yàn)證,但服務(wù)器會(huì)檢測(cè)POST過來的數(shù)據(jù)是無效的,表單會(huì)重新顯示錯(cuò)誤信息。

參考文檔

個(gè)人博客

我的個(gè)人博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容