WebApi学习笔记05:使用webapi模板--实体类--EF--迁移--Knockout

1.Web项目

1.1概述

本例主要介绍EF,数据初始化迁移,Knockout.js使用……

1.2创建项目

1.3添加实体类

在Models文件夹下,先添加一个Author.cs类,其代码:

using System.ComponentModel.DataAnnotations;

namespace WebApi05.Models
{
    public class Author
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
    }
}

在Models文件夹下,再添加一个Book.cs类,其代码:

using System.ComponentModel.DataAnnotations;

namespace WebApi05.Models
{
    public class Book
    {
        public int Id { get; set; }
        [Required]
        public string Title { get; set; }
        public int Year { get; set; }
        public decimal Price { get; set; }
        public string Genre { get; set; }

        // 外键
        public int AuthorId { get; set; }
        // 导航属性
        public Author Author { get; set; }
    }
}

1.4添加控制器

这次我们使用基架模板来创建控制器。如果需要模型类和上下文类刚新建的,需要先生成一下项目,才能使得基架识得其类。

在Controllers文件夹上右键,添加-》控制器:

选择模板后,点添加:

这里先点“数据上下文类”后的+号,来新建数据库上下文设置:

然后勾选异步:

这样基架自动为项目添加了Controllers\AuthorsController.cs和Models\EFContext.cs两个类,并在Web.config中添加数据库连接字符串。

由于生成的EFContext.cs是新建的,我们又需要先生成项目。再来创建Book控制器。创建步骤和前面一样:

1.4启用迁移

vs里,工具-》NuGet程序包管理器-》程序包管理器控制器台:

输入命令:get-help migration 来查看帮助。

启用迁移命令:

注意:选择默认项目,也就是在那个项目进行迁移。

命令执行后,在根目录下生成一个Migrations文件夹,里面有一个Configuration.cs类。

1.5添加迁移

先设置初始化数据,把Migrations\Configuration.cs代码修改为:

using System.Data.Entity.Migrations;
using WebApi05.Models;

namespace WebApi05.Migrations
{
    internal sealed class Configuration : DbMigrationsConfiguration<EFContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(EFContext context)
        {
            context.Authors.AddOrUpdate(x => x.Id,
                  new Author() { Id = 1, Name = "Jane Austen" },
                  new Author() { Id = 2, Name = "Charles Dickens" },
                  new Author() { Id = 3, Name = "Miguel de Cervantes" });

            context.Books.AddOrUpdate(x => x.Id,
                  new Book()
                  {
                      Id = 1,
                      Title = "Pride and Prejudice",
                      Year = 1813,
                      AuthorId = 1,
                      Price = 9.99M,
                      Genre = "Comedy of manners"
                  },
                  new Book()
                 {
                     Id = 2,
                     Title = "Northanger Abbey",
                     Year = 1817,
                     AuthorId = 1,
                     Price = 12.95M,
                     Genre = "Gothic parody"
                 },
                  new Book()
                 {
                     Id = 3,
                     Title = "David Copperfield",
                     Year = 1850,
                     AuthorId = 2,
                     Price = 15,
                     Genre = "Bildungsroman"
                 },
                  new Book()
                  {
                      Id = 4,
                      Title = "Don Quixote",
                      Year = 1617,
                      AuthorId = 3,
                      Price = 8.95M,
                      Genre = "Picaresque"
                  });
        }
    }
}

添加迁移命令:

执行成功后,这是Migrations文件夹下会多出一个:时间戳+FirstInitData.cs类。

1.6更新数据库

输入命令:

1.7查看数据库

我们看迁移数据库是否OK。VS里,视图-》服务器资源管理器:

1.8配置迁移

通过迁移“三步走”(启用,添加,更新),我们可以手动来迁移数据库。但有时根据需要是否进行迁移初始化数据,

能不能通过配置文件了来决定(也可以在程序入口写代码执行方法,但这样改动后需要重新编译项目)。

打开Web.config,在 <entityFramework>节点下,添加下面代码:

    <contexts>
      <!--上下文类命名空间,项目程序集名称-->
      <!--<context type="WebApi05.Models.EFContext,WebApi05" disableDatabaseInitialization="false">-->
      <context type="WebApi05.Models.EFContext,WebApi05" >
        <!--初始数据命名空间,项目程序集名称-->
        <databaseInitializer type="WebApi05.Models.InitData,WebApi05" />
      </context>
    </contexts>

在Models文件夹下,添加InitData.cs类,其代码:

using System.Data.Entity;
using System.Data.Entity.Migrations;

namespace WebApi05.Models
{
    public class InitData : DropCreateDatabaseIfModelChanges<EFContext>
    {
        protected override void Seed(EFContext context)
        {
            context.Authors.AddOrUpdate(x => x.Id,
                  new Author() { Id = 1, Name = "Jane Austen" },
                  new Author() { Id = 2, Name = "Charles Dickens" },
                  new Author() { Id = 3, Name = "Miguel de Cervantes" });

            context.Books.AddOrUpdate(x => x.Id,
                  new Book()
                  {
                      Id = 1,
                      Title = "Pride and Prejudice",
                      Year = 1813,
                      AuthorId = 1,
                      Price = 9.99M,
                      Genre = "Comedy of manners"
                  },
                  new Book()
                  {
                      Id = 2,
                      Title = "Northanger Abbey",
                      Year = 1817,
                      AuthorId = 1,
                      Price = 12.95M,
                      Genre = "Gothic parody"
                  },
                  new Book()
                  {
                      Id = 3,
                      Title = "David Copperfield",
                      Year = 1850,
                      AuthorId = 2,
                      Price = 15,
                      Genre = "Bildungsroman"
                  },
                  new Book()
                  {
                      Id = 4,
                      Title = "Don Quixote",
                      Year = 1617,
                      AuthorId = 3,
                      Price = 8.95M,
                      Genre = "Picaresque"
                  });
        }
    }
}

测试:删除原来数据或更改连接字符串中数据库名称。这样程序运行涉及到数据库访问时,就可以自动迁移了。

1.9查看生成SQL语句

修改Models\EFContext.cs

using System.Data.Entity;
using System.Diagnostics;

namespace WebApi05.Models
{
    public class EFContext : DbContext
    {

        public EFContext()
            : base("name=EFContext")
        {
            this.Database.Log = s => Debug.WriteLine(s);
        }

      public  DbSet<Author> Authors { get; set; }
      public  DbSet<Book> Books { get; set; }
    }
}

打开vs里,视图-》输出(窗户),运行网站地址http://localhost:8935/api/books  这时输出窗户可以看到生成的SQL语句:

查询结果是(可以使用fiddler工具看JSON格式):

可以看到Author数据(模型导航属性)没有加载。修改Controllers\BooksController.cs中的GetBooks(),如下:

        // GET: api/Books
        public IQueryable<Book> GetBooks()
        {
            return db.Books.Include(b => b.Author);
        }

再一次运行:

再看VS输出窗户:

如果在模型导航属性加入virtual关键字,这是查询为延迟加载(也就是多次访问数据库查询),这里就不演示。(在专门EF系列中再介绍)

1.10数据传输DTO

在Models文件夹下,创建BookDTO.cs,其代码:

namespace WebApi05.Models
{
    public class BookDTO
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string AuthorName { get; set; }
    }
}

在Models文件夹下,创建BookDetailDTO.cs,其代码:

namespace WebApi05.Models
{
    public class BookDetailDTO
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int Year { get; set; }
        public decimal Price { get; set; }
        public string AuthorName { get; set; }
        public string Genre { get; set; }
    }
}

1.11修改控制器
打开Controllers\BooksController.cs,修改GetBooks()和GetBook(int id):

        // GET api/Books
        public IQueryable<BookDTO> GetBooks()
        {
            var books = from b in db.Books
                        select new BookDTO()
                        {
                            Id = b.Id,
                            Title = b.Title,
                            AuthorName = b.Author.Name
                        };
            return books;
        }
        // GET api/Books/5
        [ResponseType(typeof(BookDetailDTO))]
        public async Task<IHttpActionResult> GetBook(int id)
        {
            var book = await db.Books.Include(b => b.Author).Select(b =>
                new BookDetailDTO()
                {
                    Id = b.Id,
                    Title = b.Title,
                    Year = b.Year,
                    Price = b.Price,
                    AuthorName = b.Author.Name,
                    Genre = b.Genre
                }).SingleOrDefaultAsync(b => b.Id == id);
            if (book == null)
            {
                return NotFound();
            }

            return Ok(book);
        } 

再一次运行:

再看vs输出生成SQL语句(简洁):

打开Controllers\BooksController.cs,修改PostBook(Book book):

        [ResponseType(typeof(Book))]
        public async Task<IHttpActionResult> PostBook(Book book)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Books.Add(book);
            await db.SaveChangesAsync();

            db.Entry(book).Reference(x => x.Author).Load();

            var dto = new BookDTO()
            {
                Id = book.Id,
                Title = book.Title,
                AuthorName = book.Author.Name
            };

            return CreatedAtRoute("DefaultApi", new { id = book.Id }, dto);
        }

1.12Knockoutjs

安装knockoutjs(简称KO):

在Scripts文件夹下,添加app.js,其代码:

var ViewModel = function () {
    var self = this;
    self.books = ko.observableArray();//保存书籍的列表
    self.error = ko.observable();//包含一条错误消息,如果 AJAX 调用失败

    var booksUri = ‘/api/books/‘;

    //AJAX助手方法
    function ajaxHelper(uri, method, data) {
        self.error(‘‘); // Clear error message
        return $.ajax({
            type: method,
            url: uri,
            dataType: ‘json‘,
            contentType: ‘application/json‘,
            data: data ? JSON.stringify(data) : null
        }).fail(function (jqXHR, textStatus, errorThrown) {
            self.error(errorThrown);
        });
    }

    //获取所有书籍方法
    function getAllBooks() {
        ajaxHelper(booksUri, ‘GET‘).done(function (data) {
            self.books(data);
        });
    }

    //获取初始数据
    getAllBooks();
};

ko.applyBindings(new ViewModel());

打开 App_Start/BundleConfig.cs。将下面的代码添加到 RegisterBundles ()中:

            bundles.Add(new ScriptBundle("~/bundles/app").Include(
              "~/Scripts/knockout-{version}.js",
              "~/Scripts/app.js"));

1.13修改视图
把Views\Home\Index.cshtml,修改为:

@section scripts {
    @Scripts.Render("~/bundles/app")
}

<div class="page-header">
    <h1>书籍展示</h1>
</div>

<div class="row">

    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h2 class="panel-title">书籍列表</h2>
            </div>
            <div class="panel-body">
                <ul class="list-unstyled" data-bind="foreach: books">
                    <li>
                        <strong><span data-bind="text: AuthorName"></span></strong>: <span data-bind="text: Title"></span>
                        <small><a href="#">详细</a></small>
                    </li>
                </ul>
            </div>
        </div>
        <div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error"></p></div>
    </div>

    <div class="col-md-4">
        <!-- TODO: Book details -->
    </div>

    <div class="col-md-4">
        <!-- TODO: Add new book -->
    </div>
</div>

运行网站:

注:上面详细页,并没有“激活”。

1.14显示详细页

修改Scripts\app.js,其代码为:

var ViewModel = function () {
    var self = this;
    self.books = ko.observableArray();//保存书籍的列表
    self.error = ko.observable();//包含一条错误消息,如果 AJAX 调用失败
    self.detail = ko.observable();//保存书籍详细

    var booksUri = ‘/api/books/‘;

    //AJAX助手方法
    function ajaxHelper(uri, method, data) {
        self.error(‘‘); // Clear error message
        return $.ajax({
            type: method,
            url: uri,
            dataType: ‘json‘,
            contentType: ‘application/json‘,
            data: data ? JSON.stringify(data) : null
        }).fail(function (jqXHR, textStatus, errorThrown) {
            self.error(errorThrown);
        });
    }

    //获取所有书籍方法
    function getAllBooks() {
        ajaxHelper(booksUri, ‘GET‘).done(function (data) {
            self.books(data);
        });
    }

    //获取书籍详细方法
    self.getBookDetail = function (item) {
        ajaxHelper(booksUri + item.Id, ‘GET‘).done(function (data) {
            self.detail(data);
        });
    }

    //获取初始数据
    getAllBooks();
};

ko.applyBindings(new ViewModel());

继续把Views\Home\Index.cshtml,修改为:

@section scripts {
    @Scripts.Render("~/bundles/app")
}
<div class="page-header">
    <h1>书籍展示</h1>
</div>
<div class="row">

    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h2 class="panel-title">书籍列表</h2>
            </div>
            <div class="panel-body">
                <ul class="list-unstyled" data-bind="foreach: books">
                    <li>
                        <strong><span data-bind="text: AuthorName"></span></strong>: <span data-bind="text: Title"></span>
                        <small><a href="#" data-bind="click: $parent.getBookDetail">详细</a></small>
                    </li>
                </ul>
            </div>
        </div>
        <div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error"></p></div>
    </div>

    <!-- ko if:detail() -->
    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h2 class="panel-title">详细</h2>
            </div>
            <table class="table">
                <tr><td>Author</td><td data-bind="text: detail().AuthorName"></td></tr>
                <tr><td>Title</td><td data-bind="text: detail().Title"></td></tr>
                <tr><td>Year</td><td data-bind="text: detail().Year"></td></tr>
                <tr><td>Genre</td><td data-bind="text: detail().Genre"></td></tr>
                <tr><td>Price</td><td data-bind="text: detail().Price"></td></tr>
            </table>
        </div>
    </div>
    <!-- /ko -->

    <div class="col-md-4">
        <!-- TODO: Add new book -->
    </div>
</div>

运行网站:

1.15添加一个

修改Scripts\app.js,其代码为:

var ViewModel = function () {
    var self = this;
    self.books = ko.observableArray();//保存书籍的列表
    self.error = ko.observable();//包含一条错误消息,如果 AJAX 调用失败
    self.detail = ko.observable();//保存书籍详细
    self.authors = ko.observableArray();
    self.newBook = {
        Author: ko.observable(),
        Genre: ko.observable(),
        Price: ko.observable(),
        Title: ko.observable(),
        Year: ko.observable()
    }

    var authorsUri = ‘/api/authors/‘;

    //获取所有作者
    function getAuthors() {
        ajaxHelper(authorsUri, ‘GET‘).done(function (data) {
            self.authors(data);
        });
    }

    //添加书籍
    self.addBook = function (formElement) {
        var book = {
            AuthorId: self.newBook.Author().Id,
            Genre: self.newBook.Genre(),
            Price: self.newBook.Price(),
            Title: self.newBook.Title(),
            Year: self.newBook.Year()
        };

        ajaxHelper(booksUri, ‘POST‘, book).done(function (item) {
            self.books.push(item);
        });
    }

    var booksUri = ‘/api/books/‘;

    //AJAX助手方法
    function ajaxHelper(uri, method, data) {
        self.error(‘‘); // Clear error message
        return $.ajax({
            type: method,
            url: uri,
            dataType: ‘json‘,
            contentType: ‘application/json‘,
            data: data ? JSON.stringify(data) : null
        }).fail(function (jqXHR, textStatus, errorThrown) {
            self.error(errorThrown);
        });
    }

    //获取所有书籍方法
    function getAllBooks() {
        ajaxHelper(booksUri, ‘GET‘).done(function (data) {
            self.books(data);
        });
    }

    //获取书籍详细方法
    self.getBookDetail = function (item) {
        ajaxHelper(booksUri + item.Id, ‘GET‘).done(function (data) {
            self.detail(data);
        });
    }

    //获取初始数据
    getAllBooks();
    getAuthors();
};

ko.applyBindings(new ViewModel());

把Views\Home\Index.cshtml,修改为:

@section scripts {
    @Scripts.Render("~/bundles/app")
}
<div class="page-header">
    <h1>书籍展示</h1>
</div>
<div class="row">

    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h2 class="panel-title">书籍列表</h2>
            </div>
            <div class="panel-body">
                <ul class="list-unstyled" data-bind="foreach: books">
                    <li>
                        <strong><span data-bind="text: AuthorName"></span></strong>: <span data-bind="text: Title"></span>
                        <small><a href="#" data-bind="click: $parent.getBookDetail">详细</a></small>
                    </li>
                </ul>
            </div>
        </div>
        <div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error"></p></div>
    </div>

    <!-- ko if:detail() -->
    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h2 class="panel-title">详细</h2>
            </div>
            <table class="table">
                <tr><td>Author</td><td data-bind="text: detail().AuthorName"></td></tr>
                <tr><td>Title</td><td data-bind="text: detail().Title"></td></tr>
                <tr><td>Year</td><td data-bind="text: detail().Year"></td></tr>
                <tr><td>Genre</td><td data-bind="text: detail().Genre"></td></tr>
                <tr><td>Price</td><td data-bind="text: detail().Price"></td></tr>
            </table>
        </div>
    </div>
    <!-- /ko -->

    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h2 class="panel-title">Add Book</h2>
            </div>

            <div class="panel-body">
                <form class="form-horizontal" data-bind="submit: addBook">
                    <div class="form-group">
                        <label for="inputAuthor" class="col-sm-2 control-label">Author</label>
                        <div class="col-sm-10">
                            <select data-bind="options:authors, optionsText: ‘Name‘, value: newBook.Author"></select>
                        </div>
                    </div>

                    <div class="form-group" data-bind="with: newBook">
                        <label for="inputTitle" class="col-sm-2 control-label">Title</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="inputTitle" data-bind="value:Title" />
                        </div>

                        <label for="inputYear" class="col-sm-2 control-label">Year</label>
                        <div class="col-sm-10">
                            <input type="number" class="form-control" id="inputYear" data-bind="value:Year" />
                        </div>

                        <label for="inputGenre" class="col-sm-2 control-label">Genre</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="inputGenre" data-bind="value:Genre" />
                        </div>

                        <label for="inputPrice" class="col-sm-2 control-label">Price</label>
                        <div class="col-sm-10">
                            <input type="number" step="any" class="form-control" id="inputPrice" data-bind="value:Price" />
                        </div>
                    </div>
                    <button type="submit" class="btn btn-default">Submit</button>
                </form>
            </div>
        </div>
    </div>
</div>

网站运行:

2.小结

本例重点掌握EF,数据迁移,使用DTO,Knockout绑定模型输出DOM上。

随着学习深度,是不是越来越接近高大上感觉了。到目前为止,还是皮毛哦。

时间: 2024-11-05 19:37:22

WebApi学习笔记05:使用webapi模板--实体类--EF--迁移--Knockout的相关文章

WebApi学习笔记01:webapi框架--控制器--路由

1.解决方案 1.1概述 一个解决方案里可以包含多个项目:也可以新建“解决方案文件夹”来逻辑(不是物理存在的文件夹)划分包含项目. 1.2创建方案 打开VS,文件->新建->项目: 2.Web项目 2.1概述 本例主要介绍安装WebApi框架,因为它几乎可以寄宿在任何项目中,先从空web模板项目,也不包含核心引用开始…… 2.1创建项目 在“解决方案资源管理器”中右键,添加->新建项目: 选择模板: 2.3安装webapi 在vs中,工具->NuGet程序包管理器->管理解决

WebAPi学习笔记之一 初识WebApi

公司这几天接了个APP项目,项目中要使用WebApi来作为服务端和客户端的数据交换层.于是我就被老板要求学习WebApi了. 对于ASP.Net相关知识为零的我来说,这无疑是一个难题.学习了这么久终于摸到了点门路.WebApi的定位很容易,它就是用来控制服务端和数据段数据转换的. WebApi包括控制器,路由,类model等几大内容: 类model:主要用来保存数据的. 控制器:主要用来根据客户端的method类型的响应, 路由:根据客户端的request URL来选择使用哪一个控制器,或者生成

Asp.Net Core WebApi学习笔记(四)-- Middleware

Asp.Net Core WebApi学习笔记(四)-- Middleware 本文记录了Asp.Net管道模型和Asp.Net Core的Middleware模型的对比,并在上一篇的基础上增加Middleware功能支持. 在演示Middleware功能之前,先要了解一下Asp.Net管道模型发生了什么样的变化. 第一部分:管道模型 1. Asp.Net管道 在之前的Asp.Net里,主要的管道模型流程如下图所示: 请求进入Asp.Net工作进程后,由进程创建HttpWorkRequest对象

SWIFT学习笔记05

1.Swift 无需写break,所以不会发生这种贯穿(fallthrough)的情况.2.//用不到变量名,可用"_"替换 for _ in 1...power { answer *= base } 3.case 可以匹配更多的类型模式,包括区间匹配(range matching),元组(tuple)和特定类型的描述. 可以这样用case case 1...3: naturalCount = "a few" 4.如果存在多个匹配,那么只会执行第一个被匹配到的 ca

C++ GUI Qt4学习笔记05

C++ GUI Qt4学习笔记05 qtc++正则表达式 QIntValidator           --  只让用户输入整数 QDoubleValidator     --  只让用户输入浮点数 QRegExpValidator    --  只让用户按照正则表达式定义好的样式进行输入 本章讲解如何使用Qt开发自定义窗口部件. 通过对一个已经存在的Qt窗口部件进行子类化或者直接对QWidget进行子类化,就可以创建自定义窗口部件. 集成自定义窗口到Qt设计师中,这样就可以像使用内置的Qt窗

Django学习笔记(二)—— 模板

疯狂的暑假学习之 Django学习笔记(二)-- 模板 参考: <The Django Book> 第四章 一.模板基础知识 1.模板是如何工作的 用 python manage.py shell 启动交互界面(因为manage.py 保存了Django的配置,如果直接python启动交互界面运行下面代码会出错) 输入下面代码 >>> from django import template >>> t = template.Template('My name

C++ Primer 学习笔记_104_特殊工具与技术 --嵌套类

特殊工具与技术 --嵌套类 可以在另一个类内部(与后面所讲述的局部类不同,嵌套类是在类内部)定义一个类,这样的类是嵌套类,也称为嵌套类型.嵌套类最常用于定义执行类. 嵌套类是独立的类,基本上与它们的外围类不相关,因此,外围类和嵌套类的对象是互相独立的.嵌套类型的对象不具备外围类所定义的成员,同样,外围类的成员也不具备嵌套类所定义的成员. 嵌套类的名字在其外围类的作用域中可见,但在其他类作用域或定义外围类的作用域中不可见.嵌套类的名字将不会与另一作用域中声明的名字冲突 嵌套类可以具有与非嵌套类相同

c++学习笔记5,多重继承中派生类的构造函数与析构函数的调用顺序(二)

现在来测试一下在多重继承,虚继承,MI继承中虚继承中构造函数的调用情况. 先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace std; class base { public: base() { cout<<"base created!"<<endl; }

C++ Primer 学习笔记_66_面向对象编程 --定义基类和派生类[续]

算法旨在用尽可能简单的思路解决问题,理解算法也应该是一个越看越简单的过程,当你看到算法里的一串概念,或者一大坨代码,第一感觉是复杂,此时不妨从例子入手,通过一个简单的例子,并编程实现,这个过程其实就可以理解清楚算法里的最重要的思想,之后扩展,对算法的引理或者更复杂的情况,对算法进行改进.最后,再考虑时间和空间复杂度的问题. 了解这个算法是源于在Network Alignment问题中,图论算法用得比较多,而对于alignment,特别是pairwise alignment, 又经常遇到maxim