1. Web Api là gì
Asp.net Web Api là một framework giúp cho việc xây dựng HTTP service một cách dễ dàng. Chúng có thể phát triển cho nhiều clients khác nhau như trình duyệt, mobile app. Web api là một nền tảng để phát triển các ứng dụng dựa trên Restfull service trong .Net. Các bạn có thể hình dung vị trí của Asp.net Api trong .Net như hình bên dưới:
2. Ứng dụng demo
Công nghệ sử dụng:
- Visual studio 2013
- .Net 4.6
- Web Api 2.0
- Entity framework 6.0
- Knockout js
2.1. Tạo project
- Mở Visual studio
- Chọn New project
- Tại cửa sổ dialog chọn Asp.net web applicaltion
- Tiếp đến chọn Web API như hình bên dưới
2.2. Tạo data models
Trong ứng dựng demo này tôi sẽ tạo 2 model category và product. Để tạo một model mới, chúng ta click chuột phải tại thư mục Models chọn “New” sau đó chọn “class” như cách tạo các class thông thường.
– Category class :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class="hljs-keyword">using</span> System.ComponentModel.DataAnnotations; <span class="hljs-keyword">namespace</span> <span class="hljs-title">WebAPIDemo.Models</span> { <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Category</span> { <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } [Required] <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } } } |
– Product class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<span class="hljs-keyword">using</span> System.ComponentModel.DataAnnotations; <span class="hljs-keyword">namespace</span> <span class="hljs-title">WebAPIDemo.Models</span> { <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Product</span> { <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } [Required] <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> CategoryId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <span class="hljs-keyword">public</span> <span class="hljs-keyword">virtual</span> Category Category { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } } } |
Lưu ý với Entity Framework 6.0, nó đã hỗ trợ cơ chế lazy loading. Trong demo này đối tượng Product có khóa ngoại là CategoryId, đồng thời nó có thể truy cập tới Category thông qua thuộc tính Category. Để có lazy loading chúng ta cần thêm keyword vitual cho thuộc tính này. Chúng ta có thể hiểu lazy loading trong trường hợp này như sau:
1 2 3 |
db.products.ToList() <span class="hljs-comment">// đối tượng Category sẽ không được load cùng danh sách products</span> db.products[<span class="hljs-number">0</span>].Category <span class="hljs-comment">// chỉ khi truy suất vào một product cụ thể Category mới được load</span> |
2.3. Tạo controller
- Click chuột phải tại thư mục Controller => chọn “Add” => chọn “Controller”
- Tại cửa sổ “Add Scaffold” chúng ta chọn “Web API 2 Controller with actions, using Entity Framework”.
Tại cửa sổ “Add controller”
- Tại Model class dropdownlist, chọn Category class.
- Tích chọn “Use async controller actions”.
- Trong mục “Controller Name” tên controller tự động được điền vào.
Tiếp đến click (+) button ở mục “Data context Class”, tại cửa sổ “New Data Context” gõ tên Data context class theo mong muốn. Ở đây tôi tạo class có tên : WebAPIDemoContext. ProductsController được tạo tương tự như các bước trên.
2.4. Làm việc với Entity framework
Trong demo này tôi sử dụng Code First Migrations để init data và thao tác với Sql server. Trước tiên cần enable migration, để làm điều này chúng ta thực hiện như sau:
Chọn Tool => chọn Nuget Package Manager => Chọn Package Manage Console. Trong cửa sổ Package Manage Console gõ lệnh:
1 2 |
Enable-Migrations |
Một thư mục Migrations được tạo ra cùng một file có tên là “Configuration”. Tiếp theo chúng ta sẽ update method “Seed trong class này để init data. Đây là method xây dựng sẵn trong class DbContext của Entity Framework và chúng ta hoàn toàn có thể ghi đè theo trường hợp sử dụng
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Seed</span>(<span class="hljs-params">WebAPIDemoContext context</span>) </span>{ context.Categories.AddOrUpdate(x => x.Id, <span class="hljs-keyword">new</span> Category { Id = <span class="hljs-number">1</span>, Name = <span class="hljs-string">"Mobile"</span> }, <span class="hljs-keyword">new</span> Category { Id = <span class="hljs-number">2</span>, Name = <span class="hljs-string">"Tablet"</span> }, <span class="hljs-keyword">new</span> Category { Id = <span class="hljs-number">3</span>, Name = <span class="hljs-string">"Smart watch"</span> } ); context.Products.AddOrUpdate(x => x.Id, <span class="hljs-keyword">new</span> Product { Id = <span class="hljs-number">1</span>, Name = <span class="hljs-string">"Product 1"</span>, CategoryId = <span class="hljs-number">1</span>, Price = <span class="hljs-number">9.99</span>M, }, <span class="hljs-keyword">new</span> Product { Id = <span class="hljs-number">2</span>, Name = <span class="hljs-string">"Product 2"</span>, CategoryId = <span class="hljs-number">1</span>, Price = <span class="hljs-number">12.95</span>M, }, <span class="hljs-keyword">new</span> Product { Id = <span class="hljs-number">3</span>, Name = <span class="hljs-string">"Product 3"</span>, CategoryId = <span class="hljs-number">2</span>, Price = <span class="hljs-number">15</span>, }, <span class="hljs-keyword">new</span> Product { Id = <span class="hljs-number">4</span>, Name = <span class="hljs-string">"Product 4"</span>, CategoryId = <span class="hljs-number">3</span>, Price = <span class="hljs-number">10</span>, } ); } |
Ok, chúng ta đã có một số data test cho demo. Bây giờ chúng ta cần một class để tạo table và insert những bản ghi trên vào sql server, để làm điều đó ta gõ lệnh:
1 2 3 |
Add-Migration Initial Update-Database |
Một file migarion được tạo ra với code được tự động generate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<span class="hljs-keyword">namespace</span> <span class="hljs-title">WebAPIDemo.Migrations</span> { <span class="hljs-keyword">using</span> System; <span class="hljs-keyword">using</span> System.Data.Entity.Migrations; <span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Initial</span> : <span class="hljs-title">DbMigration</span> { <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Up</span>() </span>{ CreateTable( <span class="hljs-string">"dbo.Categories"</span>, c => <span class="hljs-keyword">new</span> { Id = c.Int(nullable: <span class="hljs-literal">false</span>, identity: <span class="hljs-literal">true</span>), Name = c.String(nullable: <span class="hljs-literal">false</span>), }) .PrimaryKey(t => t.Id); CreateTable( <span class="hljs-string">"dbo.Products"</span>, c => <span class="hljs-keyword">new</span> { Id = c.Int(nullable: <span class="hljs-literal">false</span>, identity: <span class="hljs-literal">true</span>), Name = c.String(nullable: <span class="hljs-literal">false</span>), Price = c.Decimal(nullable: <span class="hljs-literal">false</span>, precision: <span class="hljs-number">18</span>, scale: <span class="hljs-number">2</span>), CategoryId = c.Int(nullable: <span class="hljs-literal">false</span>), }) .PrimaryKey(t => t.Id) .ForeignKey(<span class="hljs-string">"dbo.Categories"</span>, t => t.CategoryId, cascadeDelete: <span class="hljs-literal">true</span>) .Index(t => t.CategoryId); } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Down</span>() </span>{ DropForeignKey(<span class="hljs-string">"dbo.Products"</span>, <span class="hljs-string">"CategoryId"</span>, <span class="hljs-string">"dbo.Categories"</span>); DropIndex(<span class="hljs-string">"dbo.Products"</span>, <span class="hljs-keyword">new</span>[] { <span class="hljs-string">"CategoryId"</span> }); DropTable(<span class="hljs-string">"dbo.Products"</span>); DropTable(<span class="hljs-string">"dbo.Categories"</span>); } } } |
Với đoạn code ở trên, Code First sẽ tự động kết nối, tạo database, table và insert data tới sql server trong lần khởi chạy đầu tiền của ứng dụng, những lần sau đó nếu có thay đổi migration DB sẽ được update ngược lại chúng sẽ không tạo lại nữa. Bây giờ chúng ta nhấn F5 để chạy web, sau đó kiểm tra database trong Server Exploer của Visual studio. Nếu có đầy đủ table và dữ liệu như hình bên dưới thì việc khởi tạo DB đã thành công.
2.5. Xây dựng client với với knockout js
Trong ứng dụng này, tôi sử dụng Knockout js để xây dựng phía client. Knockout js là một thư viện javascript sử dụng mô hình Model-View-ViewModel (MVVM). Các bạn có thể tìm hiểu thêm tại website Knockout. Có thể hình dung mô hình hoạt động của ứng dụng như sau:
Chúng ta sẽ xây dựng 3 chức năng cơ bản cho ứng dụng demo :
- Show danh sách sản phẩm
- Show chi tiết sản phẩm
- Thêm mới sản
Trước tiên, chúng ta add knockout js tới ứng dụng. Trong Package manager console gõ lệnh:
1 2 |
Install-Package knockoutjs |
Tạo view model:
Ở đây tôi sẽ tạo một file với tên là app.js trong thư mực Scripts của dự án để viết các hàm thao tác dự liệu với Server thông qua Knockout JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
<span class="hljs-keyword">var</span> ViewModel = <span class="hljs-function"><span class="hljs-keyword">function</span> () </span>{ <span class="hljs-keyword">var</span> self = <span class="hljs-keyword">this</span>; self.products = ko.observableArray(); self.error = ko.observable(); <span class="hljs-keyword">var</span> productsUri = <span class="hljs-string">'/api/products/'</span>; <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ajaxHelper</span>(<span class="hljs-params">uri, method, data</span>) </span>{ self.error(<span class="hljs-string">''</span>); <span class="hljs-comment">// Clear error message</span> <span class="hljs-keyword">return</span> $.ajax({ <span class="hljs-attr">type</span>: method, <span class="hljs-attr">url</span>: uri, <span class="hljs-attr">dataType</span>: <span class="hljs-string">'json'</span>, <span class="hljs-attr">contentType</span>: <span class="hljs-string">'application/json'</span>, <span class="hljs-attr">data</span>: data ? <span class="hljs-built_in">JSON</span>.stringify(data) : <span class="hljs-literal">null</span> }).fail(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">jqXHR, textStatus, errorThrown</span>) </span>{ self.error(errorThrown); }); } <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAllProducts</span>() </span>{ ajaxHelper(productsUri, <span class="hljs-string">'GET'</span>).done(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) </span>{ self.products(data); }); } <span class="hljs-comment">// Fetch the initial data.</span> getAllProducts(); self.detail = ko.observable(); self.getProductDetail = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">item</span>) </span>{ ajaxHelper(productsUri + item.Id, <span class="hljs-string">'GET'</span>).done(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) </span>{ self.detail(data); }); } self.categories = ko.observableArray(); self.newProduct= { <span class="hljs-attr">Category</span>: ko.observable(), <span class="hljs-attr">Name</span>: ko.observable(), <span class="hljs-attr">Price</span>: ko.observable(), } <span class="hljs-keyword">var</span> categoriesUri = <span class="hljs-string">'/api/categories/'</span>; <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCategories</span>() </span>{ ajaxHelper(categoriesUri, <span class="hljs-string">'GET'</span>).done(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) </span>{ self.categories(data); }); } self.addProduct = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">formElement</span>) </span>{ <span class="hljs-keyword">var</span> product = { <span class="hljs-attr">CategoryId</span>: self.newProduct.Category().Id, <span class="hljs-attr">Name</span>: self.newProduct.Name(), <span class="hljs-attr">Price</span>: self.newProduct.Price(), }; ajaxHelper(productsUri, <span class="hljs-string">'POST'</span>, product).done(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">item</span>) </span>{ self.products.push(item); }); } getCategories(); }; ko.applyBindings(<span class="hljs-keyword">new</span> ViewModel()); |
Binding trên view:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"panel panel-default"</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"panel-heading"</span>></span> <span class="hljs-tag"><<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"panel-title"</span>></span>Products<span class="hljs-tag"></<span class="hljs-name">h2</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"panel-body"</span>></span> <span class="hljs-tag"><<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"list-unstyled"</span> <span class="hljs-attr">data-bind</span>=<span class="hljs-string">"foreach: products"</span>></span> <span class="hljs-tag"><<span class="hljs-name">li</span>></span> <span class="hljs-tag"><<span class="hljs-name">strong</span>></span><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">data-bind</span>=<span class="hljs-string">"text: CategoryName"</span>></span><span class="hljs-tag"></<span class="hljs-name">span</span>></span><span class="hljs-tag"></<span class="hljs-name">strong</span>></span>: <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">data-bind</span>=<span class="hljs-string">"text: Name"</span>></span><span class="hljs-tag"></<span class="hljs-name">span</span>></span> <span class="hljs-tag"><<span class="hljs-name">small</span>></span><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">data-bind</span>=<span class="hljs-string">"click: $parent.getProductDetail"</span>></span>Details<span class="hljs-tag"></<span class="hljs-name">a</span>></span><span class="hljs-tag"></<span class="hljs-name">small</span>></span> <span class="hljs-tag"></<span class="hljs-name">li</span>></span> <span class="hljs-tag"></<span class="hljs-name">ul</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"></<span class="hljs-name">div</span>></span> |
Ứng dụng demo sẽ như hình bên dưới:
Trên đây tôi đã tạo một ứng dụng đơn giản dùng Asp.Net WebAPI 2.0, EF 6.0. Hy vọng rằng demo này sẽ giúp các bạn chưa biết có thể bắt đầu làm việc với Asp.net WebApi. Nó là quen thuộc nếu các bạn đã từng làm việc với Asp.Net Mvc, đồng thời nó cũng không quá phức tạp cho những ai mới làm quen. Chúc các bạn thành công.