標籤

2020年3月11日 星期三

ASP.NET Core - Add a model

᳓前言

᳓重點整理

  • 新增一個Model
  • 新增與Model對應的Scaffold
  • 初始化Migration

*重點1. 新增一個Model

  • 右鍵點擊RazorPagesMovie專案 > 加入 > 新增資料夾,命名為Models
  • 右鍵點擊Models資料夾 > 加入 > 新增項目 > Class,命名為Movie
  • 在Movie class中加入以下的屬性
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; // []
using System.Linq;
using System.Threading.Tasks;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; } // the primary key
public string Title { get; set; }
// The DataType attribute spedifies the type of the data(Date).
// With this attribute:
// 1. The user is not required to enter time information in the date field.
// 2. Only the date is displayed, not time information.
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
view raw Movie.cs hosted with ❤ by GitHub

*重點2. 新增與Model對應的Scaffold

  • 右鍵點擊Pages資料夾 > 加入 > 新增資料夾,命名為Movies
  • 右鍵點擊Movies資料夾 > 加入 > 新增Scaffold項目 > Razor頁面 > 使用Entity Framework(CRUD)的Razor頁面
  • 在模型類別(M)欄位選擇Movie(RazorPagesMovie.Models)、在資料內容類別(D)欄位選擇RazorPagesMovie.Models.RazorPagesMovieContext > 新增
  • 加入完成後會自動建立下列資料夾及檔案:
    • Data/RazorPagesMovieContext.cs
    • Pages/Movies/Create, Delete, Details, Edit, Index
  • 加入完成後會更新以下檔案:
    • Startup.cs(※新增services.AddDbContext<RazorPagesMovieContext>(...))
    • appsettings.json(※新增ConnectionStrings,可修改自動命名的資料庫名稱)

*重點3. 初始化Migration

  • 工具 > NuGet套件管理員 > 套件管理器主控台
  • 分別輸入以下兩個指令(※按下Enter送出並執行指令)
    • Add-Migration InitialCreate(※新增一個命名為InitialCreate的Migration,執行後會於Migrations資料夾建立<time-stamp>_InitialCreate.cs檔案,自動產生用來建立Movie資料表的程式碼)
    • Update-database(※執行<time-stamp>_InitialCreate.cs的Up方法,執行完畢會於C:\使用者\{user}建立資料庫檔案,含.mdf及.ldf)
  • Ctrl+F5執行網頁,於網址輸入https://localhost:{port}/Movies測試網站是否運作正常

ASP.NET Core - Layout

᳓前言

᳓重點整理

  • 預設的Layout配置
  • Layout雜談
  • 於Loyout參考多個Section

*重點1. 預設的Layout配置

  • 於/Pages/_ViewStart.cshtml可指定預設的Layout參考檔案,只要是沒有在頁面中設定Layout屬性的頁面都會自動帶入。一開始建立專案時會預設為/Pages/Shared/_Layout.cshtml(含Header、Main Container、Footer)。
  • 為了讓使用者在整個應用程式中有一致的操作感,一般而言Header及Footer每個頁面都會相同,因此透過獨立於_Layout.cshtml編輯可方便進行維護並減少冗餘的程式碼,且在整個應用程式中經常性被參考的靜態檔案也可於此統一管理。
  • 不同頁面的主要內容則分成不同檔案編輯並在Main Container(※程式碼@RenderBody(),此為必須且只能插入一個,若無將會觸發Exception)中顯示。

*重點2. Layout雜談

  • Layout屬性在頁面中並不是必需的,可不設定,或者設定為null。
  • 在同一應用程式中可定義不只一個_Layout.cshtml以提供給不同頁面參考,而在頁面設定的Layout屬性會優先於_ViewStart.cshtml所設定的。
  • _Layout.cshtml可建立在Shared以外的資料夾,但由於預設的搜尋路徑為/Shared、/Pages/Shared、/Views/Shared等,因此除此之外的路徑需要填寫完整的路徑才能正確參考。

*重點3. 於Layout參考多個Section

  • 與@RenderBody()不同,透過多個@RenderSection()可於同一個Layout參考多個Section,可設定是否為必須,但若設定為必須卻搜尋不到參考內容時將會觸發,例如Exception@RenderSection("Scripts", required: true)。
  • Section只能被定義並使用在Layout。
  • Section可以藉由Partial Tag Helper參考特定的.cshtml,例如@section Scripts {<partial name="_ValidationScriptsPartial" />}。

2019年12月4日 星期三

ASP.NET Core - Get started

᳓前言

᳓重點整理

  • 建立一個Razor Pages網頁應用程式專案
  • 簡介專案中的檔案

*重點1. 建立專案

  • 開始使用 > 建立新的專案
  • ASP.NET Core Web應用程式(※需確認最左下方的標籤為所要使用的語言,筆者曾不小心建立成VB的專案) > 下一步
  • 修改專案名稱(RazorPagesMovie) > 瀏覽並選擇要建立專案資料夾的路徑 > 建立
  • .NET Core > ASP.NET Core 3.1 > Web應用程式 > 建立
  • 完成

*重點2. 簡介專案檔案

  • pages資料夾:含Razor檢視(.cshtml)及Razor頁面(.cshtml、.cshtml.cs),.cshtml含C#程式碼,可處理頁面的事件。(關於_Layout.cshtml等細節請參考"ASP.NET Core - Layout")
  • wwwroot資料夾:預設加入bootstrap及jquery框架,因此有許多靜態檔案,比如.js、.css、.html等。
  • appSettings.json:各種組態資料,比如資料庫的連接字串。
  • Program.cs:程式的進入點。
  • Startup.cs:設定應用程式的表現(behavior),含Configure Method(建立處理應用程式要求的pipeline)及ConfigureServices Method(此為選擇性,可將應用程式的各種服務於此註冊)。

2018年6月4日 星期一

ASP.NET MVC 5 - Adding Validation

᳓前言
本篇文章將"Adding Validation"整理而成。

᳓重點整理
  • 在Model類別中加入驗證邏輯,如此一來,在這個應用程式中對於該Model的所有新增、修改都會依照該邏輯進行驗證。
  • ASP.NET MVC的其中一個核心設計概念為DRY(Don't Repeat Yourself),意思是只需要指定功能或行為一次之後,在應用程式中有使用到的各部分就能夠體現出來。好處是能夠減少需要撰寫的程式碼,而且可以有效降低錯誤發生並方便維護。
᳓實作1. 修改Models/Movie.cs
首先要確認目前資料表的欄位,之後才能做比較。
開啟伺服器管理員->展開資料連接->展開Movies.mdf->右鍵dbo.Movies->選擇開啟資料表定義
尚未修改的dbo.Movies
修改Movie.cs,為各個欄位新增驗證邏輯。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; // Display
using System.Data.Entity; // DbContext
using System.Linq;
using System.Web;
namespace WebApplication1.Models
{
public class Movie
{
public int ID { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Rease Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
//[DisplayFormat(DataFormatString = "0:d", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]=[a-zA-Z'\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]=[a-zA-Z'\s]*$")]
[StringLength(5)]
public string Rating { get; set; }
}
public class MovieDBContext : DbContext
{
public DbSet<Movie> Movies { get; set; }
}
}
view raw Movie.cs hosted with ❤ by GitHub
在套件管理器主控台輸入Add-Midgration AddDataAnnotations,便會自動產生以下程式碼。
namespace WebApplication1.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class AddDataAnnotations : DbMigration
{
public override void Up()
{
AlterColumn("dbo.Movies", "Title", c => c.String(maxLength: 60));
AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false, maxLength: 30));
AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
}
public override void Down()
{
AlterColumn("dbo.Movies", "Rating", c => c.String());
AlterColumn("dbo.Movies", "Genre", c => c.String());
AlterColumn("dbo.Movies", "Title", c => c.String());
}
}
}
在套件管理器主控台輸入Update-database,執行完畢後再次查看資料表定義會發現已經變更完成。如此一來,當應用程式在變更資料表資料(呼叫SaveChanges方法)時,會優先判斷資料是否符合驗證邏輯,若是不符(比如必填欄位沒有資料),便會拋出DbEntityValidationException的例外狀況,防止資料庫被存入不好的資料,增加應用程式的強健性。
修改後的dbo.Movies(StringLength:設定字串長度、Display:設定欄位顯示名稱、DataType:設定提示View如何顯示特定的資料、DisplayFormat:指定資料顯示的格式、Required:設定是否為必填項目、Range:設定值域、RegularExpression:設定輸入規則,可防止輸入空格、數字、特殊符號等)

2018年4月3日 星期二

ASP.NET MVC 5 - Adding a New Field

᳓前言
本篇文章將"Adding a New Field"整理而成。

᳓重點整理
  • 在完全沒有資料庫檔案的狀況下,透過Code First建立資料庫及資料表。
  • 在已經透過Code First建立的資料庫中,進行資料表的修改。
᳓實作1. 開啟Tools->NuGet Package Manager->Package Manager Console
記得先刪除在之前的練習中自動建立的App_Data/*.mdf檔案後再進行下列的練習。

᳓實作2. 啟用Migrations-在Package Manager Console輸入指令
1. 輸入"Enable-Migrations -ContextTypeName {WebApplication1}.Models.MovieDBContext"->Enter
2. 指令執行完畢後,會在專案中自動建立Migrations資料夾,且在該資料夾中新增Configuration.cs檔案

᳓實作3. 新增Migration-修改Migrations/Configuration.cs、在Package Manager Console輸入指令
修改完Congiguration.cs之後,一定要先重建專案,否則指令會執行失敗。當"add-migration Initial"指令執行完畢後,會自動產生所變更內容的{201804021004386}_Initial.cs檔案。需要注意的是,每次進行Code First Migrations (也就是輸入下一步的update-database指令)都會執行Seed()方法中的程式碼。
namespace WebApplication1.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using WebApplication1.Models; // Movie
internal sealed class Configuration : DbMigrationsConfiguration<WebApplication1.Models.MovieDBContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(WebApplication1.Models.MovieDBContext context)
{
context.Movies.AddOrUpdate(i => i.Title,
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
}
}
}
1. 輸入"add-migration Initial"->Enter
namespace WebApplication1.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class Initial : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Movies",
c => new
{
ID = c.Int(nullable: false, identity: true),
Title = c.String(),
ReleaseDate = c.DateTime(nullable: false),
Genre = c.String(),
Price = c.Decimal(nullable: false, precision: 18, scale: 2),
})
.PrimaryKey(t => t.ID);
}
public override void Down()
{
DropTable("dbo.Movies");
}
}
}
᳓實作4. 進行Code First Migrations-在Package Manager Console輸入指令
當"update-database"指令執行完畢,便已完成資料庫、資料表的建立,可開啟瀏覽器查看。
1. 輸入"update-database"->Enter
2. 網址列輸入"http://{localhost:port}/Movies/Index"

᳓實作5. 新增Rating新欄位-修改Models/Movie.cs、Controllers/MoviesController.cs、Views/Movies/Index.cshtml、Views/Movies/Create.cshtml、
接著要在Movie.cs新增一個Rating欄位,因此MoviesController.cs的Create Action需要增加Bind的欄位、Index.cshtml及Create.cshtml需要增加輸入欄位,而其他頁面,像是Edit.cshtml、Details.cshtml、Delete.cshtml也需要修改,但在這裡省略說明。值得注意的是,修改完畢後可以成功重建專案,但在瀏覽器瀏覽時會出現下圖的錯誤訊息,那是因為Model已經修改,但資料表還沒有更新,所以會以錯誤訊息提示必須更新資料表。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; // Display
using System.Data.Entity; // DbContext
using System.Linq;
using System.Web;
namespace WebApplication1.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[Display(Name = "Rease Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
//[DisplayFormat(DataFormatString = "0:d", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
public string Rating { get; set; }
}
public class MovieDBContext : DbContext
{
public DbSet<Movie> Movies { get; set; }
}
}
view raw Movie.cs hosted with ❤ by GitHub
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
public class MoviesController : Controller
{
private MovieDBContext db = new MovieDBContext();
// GET: Movies/Index?searchString={text}
public ActionResult Index(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = (from d in db.Movies
orderby d.Genre
select d.Genre);
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst, "愛情片");
var movies = (from m in db.Movies
select m);
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(s => s.Genre == movieGenre);
}
if(!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
// GET: Movies/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// GET: Movies/Create
public ActionResult Create()
{
return View();
}
// POST: Movies/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (ModelState.IsValid)
{
db.Movies.Add(movie);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
// GET: Movies/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
// GET: Movies/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Movie movie = db.Movies.Find(id);
db.Movies.Remove(movie);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
@model IEnumerable<WebApplication1.Models.Movie>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("Index", "Movies", FormMethod.Get))
{
<p>
類型:@Html.DropDownList("movieGenre", "全部")
標題:@Html.TextBox("searchString")<br />
<input type="submit" value="搜尋" />
</p>
}
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Rating)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
view raw Index.cshtml hosted with ❤ by GitHub
@model WebApplication1.Models.Movie
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.ReleaseDate, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.ReleaseDate, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.ReleaseDate, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Genre, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Genre, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Price, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Price, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Price, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Rating, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Rating, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Rating, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
view raw Create.cshtml hosted with ❤ by GitHub

1. 網址列輸入"http://{localhost:port}/Movies/Index"

᳓實作6. 進行Code First Migrations-修改Migrations/Configuration.cs、在Package Manager Console輸入指令 為了使Model與資料表的內容一致,需要再次進行Code First Migrations,而每次進行Code First Migrations時都會重新執行一次Configuration.cs的Seed(),所以若是修改Configuration.cs便能將Rating欄位預設值輸入資料表中。值得注意的是,若之後只是修改預設值,並沒有更改欄位的話,可以直接執行"update-database"指令進行預設值的更新,不需要執行"add-migration"指令。
namespace WebApplication1.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using WebApplication1.Models; // Movie
internal sealed class Configuration : DbMigrationsConfiguration<WebApplication1.Models.MovieDBContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(WebApplication1.Models.MovieDBContext context)
{
context.Movies.AddOrUpdate(i => i.Title,
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Price = 7.99M,
Rating = "PG"
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M,
Rating = "PG"
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M,
Rating = "PG"
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M,
Rating = "PG"
}
);
}
}
}

1. 輸入"add-migration Rating"->Enter
2. 輸入"update-database"->Enter
3. 網址列輸入"http://{localhost:port}/Movies/Index"可以看到Rating欄位已經輸入預設值"PG"

2018年3月26日 星期一

ASP.NET MVC 5 - Adding a Search

᳓前言
本篇文章將"Adding a Search Method and Search View"整理而成。

᳓重點整理
  • 可直接在URL輸入參數便可作為字串進行標題的搜尋。
  • 在View加入TextBox,可輸入文字並進行標題的搜尋。
  • 在View加入DropDownList,可選擇類型並進行類型的搜尋。

᳓實作1. 修改Controllers/MoviesController.cs 

在Index Action新增searchString參數(當然也可以修改為直接利用URL Route的方式搜尋,請參考"ASP.NET MVC 5 - Adding a Controller"),並加入LINQ query程式碼。值得注意的是,LINQ query在初被定義時還沒執行,要等到Index Action回傳結果至View時才會執行。
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
public class MoviesController : Controller
{
private MovieDBContext db = new MovieDBContext();
// GET: Movies/Index?Searchstring={text}
public ActionResult Index(string searchString)
{
var movies = (from m in db.Movies
select m);
if(!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
// GET: Movies/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// GET: Movies/Create
public ActionResult Create()
{
return View();
}
// POST: Movies/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (ModelState.IsValid)
{
db.Movies.Add(movie);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
// GET: Movies/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
// GET: Movies/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Movie movie = db.Movies.Find(id);
db.Movies.Remove(movie);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}

1. 在網址列輸入"http://{localhost:port}/Movies/Index?searchString={searchString}",按下Enter重新載入頁面即會顯示影片標題內含所搜尋字串的影片。

᳓實作2. 修改Views/Movies/Index.cshtml
然而經由修改URL的方式搜尋太不直覺,因此要在Index.cshtml頁面直接加入UI,提供輸入欄位以及控制按鈕,方便使用。
@model IEnumerable<WebApplication1.Models.Movie>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm())
{
<p>
Title:@Html.TextBox("SearchString")<br />
<input type="submit" value="Filter" />
</p>
}
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
view raw Index.cshtml hosted with ❤ by GitHub

1. 一進入頁面就可以看到"BeginForm()"被編譯成<form>,其下含"TextBox()"被編譯成<input type="text">,以及<input type="submit">。(此時可直接利用UI進行搜尋,不需要添加與修改URL中的參數,Controller在執行時會自動取用對應id的元件值,若是將@Html.TextBox("searchString")改為@Html.TextBox("searchStrings")便會無法進行搜尋)

᳓實作3. 修改Views/Movies/Index.cshtml
光只是加入UI仍然沒有提供好的使用體驗,因為當使用者想要將搜尋結果分享給其他人的時候卻發現複製到的URL中並沒有附帶參數,不能夠直接顯示出搜尋的結果,所以要再進行一點修正,使用BeginForm("{Action Name}", "{Controller Name}", FormMethod.Get)。
@model IEnumerable<WebApplication1.Models.Movie>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("Index", "Movies", FormMethod.Get))
{
<p>
標題:@Html.TextBox("searchString")<br />
<input type="submit" value="搜尋" />
</p>
}
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
view raw Index.cshtml hosted with ❤ by GitHub

1. 按下搜尋按鈕後,URL會自動添加"?searchString={searchString}",因此該網址可直接複製至新頁面使用。

᳓實作4. 修改Controllers/MoviesController.cs、Views/Movies/Index.cshtml
接著練習加入下拉式選單至頁面中。利用LINQ query搜尋資料表中所有的類型,接著以Distinct()將重複的選項剔除後加入至List,最後將List的資料用ViewBag.SelectList({Collection.IEnumerable items}, "{Selected Value}")代入至View並顯示
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
public class MoviesController : Controller
{
private MovieDBContext db = new MovieDBContext();
// GET: Movies/Index?searchString={text}
public ActionResult Index(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = (from d in db.Movies
orderby d.Genre
select d.Genre);
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst, "愛情片");
var movies = (from m in db.Movies
select m);
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(s => s.Genre == movieGenre);
}
if(!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
// GET: Movies/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// GET: Movies/Create
public ActionResult Create()
{
return View();
}
// POST: Movies/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (ModelState.IsValid)
{
db.Movies.Add(movie);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
// GET: Movies/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
// GET: Movies/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Movie movie = db.Movies.Find(id);
db.Movies.Remove(movie);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
@model IEnumerable<WebApplication1.Models.Movie>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("Index", "Movies", FormMethod.Get))
{
<p>
類型:@Html.DropDownList("movieGenre", "全部")
標題:@Html.TextBox("searchString")<br />
<input type="submit" value="搜尋" />
</p>
}
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
view raw Index.cshtml hosted with ❤ by GitHub

1. 一進入頁面就可以看到"DropDownList()"被編譯成<select>,除了資料庫中的類型外還增加了"全部"的選項,且預設選取"愛情片"。

2018年3月18日 星期日

C# - Function傳值和傳址的小測試

᳓重點整理
這是function透過傳值和傳址兩種方式傳入參數的小測試。傳值是只將function的參數值,設定成與輸入變數的值相同;而傳址則是直接將輸入變數作為function的參數使用,因此會造就以下幾點不同:

  • 以傳值方式傳入的參數,在function中修改參數值也不會影響到輸入變數。
  • 以傳址方式傳入的參數,在function中修改參數值會影響到輸入變數。
  • 傳值跟傳址的function即使同名也視為不同的function,可正確執行。


᳓實作1. 建立專案
建立一個Windows Forms Application專案,並在Form拉入兩個Label、兩個Button。

᳓實作2. 修改FunctionRefTest.cs
加入兩個Button_Click事件、兩個function,並輸入以下程式碼,測試重點為在function中會修改參數值,並於function執行完畢後輸出變數值。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication_FunctionRefTest
{
public partial class FunctionRefTest : Form
{
public FunctionRefTest()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
int r = 4;
double result = CalculateArea(r);
MessageBox.Show("傳值: " + r + " " + result); // "傳值: 4 16"
}
static double CalculateArea(int r)
{
double Volume = r * r;
r = r + 1;
return Volume;
}
private void button2_Click(object sender, EventArgs e)
{
int p = 4;
double result = CalculateArea(ref p);
MessageBox.Show("傳址: " + p + " " + result); // "傳址: 5 16"
}
static double CalculateArea(ref int p)
{
double Volume = p * p;
p = p + 1;
return Volume;
}
}
}

傳值測試:輸入變數值為4,輸出時不變。

傳址測試:輸入變數值為4,輸出時已被修改。