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

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

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

參照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ù)中采用依賴注入來注入一個數(shù)據(jù)庫上下文到該控制器碍岔。數(shù)據(jù)庫上下文將被應(yīng)用到控制器中的每一個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);
}

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

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

Create.cshtml運行效果:

Details.cshtml運行效果:

Edit.cshtml運行效果:

Delete.cshtml運行效果:

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

MVC提供了傳遞強類型對象給視圖的能力恼琼,這樣為你的代碼提供了更好的編譯時檢查,并在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 會:

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

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

如果指定的User被找到,則User Model實例將被傳遞到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>

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

@model MyFirstApp.Models.User

@model指令允許你通過使用強類型的Model對象來訪問從控制器傳遞到視圖的User對象。例如神年,在Details.cshtml視圖中已维,通過使用強類型的Model對象傳遞User的每一個字段到DisplayNameForDisplayFor HTML Helper。

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

User控制器中的Index方法:

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

Index.cshtml文件最頂部:

@model IEnumerable<MyFirstApp.Models.User>

@model指令允許你訪問通過強類型的Model從控制器傳遞到視圖的User列表堂鲜。例如,在Index.cshtml視圖中护奈,在強類型的Model對象上通過foreach語句遍歷了整個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>
}

添加倉儲類

首先缔莲,新建一個Repositories文件夾。在該文件夾下定義一個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);
    }
}

接著再添加一個UserRepository來實現(xiàn)IUserRepository接口痴奏。將之前定義的UserContext.cs邏輯移到該類中磺箕,在UserRepository.cs 構(gòu)造函數(shù)中采用依賴注入來注入一個數(shù)據(jù)庫上下文(UserContext)到該倉儲類。數(shù)據(jù)庫上下文將被應(yīng)用到倉儲類中的每一個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ù)中依賴注入UserRepository

再修改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");
    }
}

注冊倉儲

通過定義Repository接口,從MVC Controller中解耦該repository類棉姐。通過注入一個UserRepository來代替直接在Controller里面實例化一個UserRepository類屠列。

為了注入一個Repository到Controller,我們必須通過DI容器來注冊它伞矩,打開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,你會發(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動作方法和路由ID動態(tài)生成HTMLhref屬性值狱杰。

查看Startup.cs中的Configure方法:

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

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

查看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是用來防止偽造請求的,會與Views/User/Edit.cshtml視圖文件生成的反偽造標(biāo)記(Token)進行配對朗和。Views/User/Edit.cshtml視圖文件通過Form Tag Helper來生成反偽造標(biāo)記(Token)错沽。

<form asp-action="Edit">

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

查看Edit.cshtml,會發(fā)現(xiàn)基架系統(tǒng)(Scaffolding System)會為User類的每一個屬性生成用來呈現(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>

基架代碼使用了多個Tag Helper方法來簡化HTML標(biāo)記千埃。

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

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

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

處理POST請求

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

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

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

在表單被POST到服務(wù)器之前,客戶端驗證會檢查所有字段上的驗證規(guī)則潦俺,如果有任何驗證錯誤拒课,則會顯示該錯誤信息,并且表單不會被發(fā)送到服務(wù)器事示。如果禁用了JS早像,將不會有客戶端驗證,但服務(wù)器會檢測POST過來的數(shù)據(jù)是無效的肖爵,表單會重新顯示錯誤信息卢鹦。

參考文檔

個人博客

我的個人博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市劝堪,隨后出現(xiàn)的幾起案子冀自,更是在濱河造成了極大的恐慌,老刑警劉巖幅聘,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凡纳,死亡現(xiàn)場離奇詭異,居然都是意外死亡帝蒿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門巷怜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葛超,“玉大人,你說我怎么就攤上這事延塑⌒逭牛” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵关带,是天一觀的道長侥涵。 經(jīng)常有香客問我,道長宋雏,這世上最難降的妖魔是什么芜飘? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮磨总,結(jié)果婚禮上嗦明,老公的妹妹穿的比我還像新娘。我一直安慰自己蚪燕,他們只是感情好娶牌,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布奔浅。 她就那樣靜靜地躺著,像睡著了一般诗良。 火紅的嫁衣襯著肌膚如雪汹桦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天鉴裹,我揣著相機與錄音舞骆,去河邊找鬼。 笑死壹罚,一個胖子當(dāng)著我的面吹牛葛作,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猖凛,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赂蠢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辨泳?” 一聲冷哼從身側(cè)響起虱岂,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎菠红,沒想到半個月后第岖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡试溯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年蔑滓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遇绞。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡键袱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摹闽,到底是詐尸還是另有隱情蹄咖,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布付鹿,位于F島的核電站澜汤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舵匾。R本人自食惡果不足惜俊抵,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纽匙。 院中可真熱鬧务蝠,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至院喜,卻和暖如春亡蓉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喷舀。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工砍濒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人硫麻。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓爸邢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拿愧。 傳聞我的和親對象是個殘疾皇子杠河,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容