《ASP.NET Core技术内幕与项目实战:基于DDD与前后端分离》读后感一

由于自己菜鸡,只能任由社会摆布,自学一段时间JAVA想去找JAVA工作,突然一份.NET Core的工作摆在面前,还好有点学历,虽然C#已经不太记得,领导仍然放我进去了。开始的一段时间,各种摸项目,也能做点东西,但是仍然感觉有些东西不理解,刚好开了个微信读书会员,就在上面找点知识看吧,就看到了这本书,这里就记录一下初步学习的总结,都是比较基础的ASP .NET Core知识,下一次再记录DDD。后面发现这本书还有视频,而且视频讲得东西更多,后续也会把视频的东西总结一下,也可能是对该篇文章有所修改。

重难点

请添加图片描述

新语法

顶级语句

可以直接在C#文件中编写入口代码,不再需要声明类和方法

全局using

建立Usings.cs全局同意管理引用,使用global using System.Text.Json;引用

simply using embeding

using会在程序结束关闭,或者使用大括号,括号结束using资源释放

using (var conn = new SqlConnection(conStr))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "select * from form";
        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {

            }
        }
    }
    
}
           ||
           ||
           ||
           ||
           ||
           \/
using var con = new SqlConnection(connStr);
con.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "select * from form";
using var reader = cmd.ExecuteReader();
while (reader.Read())
{

}

simply namespace to avoid embed

直接写namespace关键字避免命名空间嵌套,代码结构嵌套严重较难看

namespace MyNamespace;

Record

使用Record可以创建类,会重写ToString,Equals;可以使用with快速构建Record这种特殊类

#region Record
#region demo1
var p1 = new Person("a", "b");
var p2 = new Person("a", "b");
Console.WriteLine(p1);
Console.WriteLine(p1 == p2);
public record Person(string FirstName, string LastName);
#endregion

#region demo2
var u1 = new User("li", 25);
var u2 = new User("li", "56464654@qq.com", 18);
var u3 = u1 with { Email = "test_fjiort@qq.com", Age = 99 };
public record User(string UserName, string? Email, int Age)
{
    public User(string userName, int age) : this(userName, null, age)
    {

    }
}

#endregion
#endregion

异步编程

异步编程提高服务器接待请求的数量,但不会使得单个请求处理效率变高,甚至可能略又降低

await\async

使用异步方法注意点:

  • 返回值Task<T>
  • 方法一般以Async结尾
  • 调用一般在方法签名加上await

原理:

使用反编译工具查看代码

async方法会被C#编译器编译成一个类,并根据await调用把方法切分成多个状态,对async方法的调用就会被拆分成若干次对MoveNext方法的调用

线程切换

在异步方法进行await调用的等待期间,框架会把当前的线程返回给线程池,等异步方法调用执行完毕,框架会从线程池再去除一个线程,以执行后续的代码。

注意

  • 异步方法不等于多线程。
    • 如何使得代码再新线程执行呢,使用Task.Run
  • 异步方法不一定必须有async。
    • 如果一个异步方法只是对别的异步方法进行简单的调用,并没有复杂的逻辑,比如获取异步方法的返回值后再进一步的处理,就可以去掉asqyn\await关键字
  • 建议开发人员只使用异步方法,因为这样能提升系统并发处理能力
  • 若由于框架限制,我们编写的方法不能标注async
    • 那么可以在Task<T>类型对象调用Result属性来等待异步执行结束获取返回值
    • 返回值为Task,可以在Task类型对象调用Wait方法来调用异步方法并等待任务执行结束
  • 异步暂停使用Thread.Sleep
  • 异步方法中的CancellationToken对象让异步方法提前终止
  • 可以使用Task.WhenAll等待多个Task的执行结束
  • 接口中的方法或者抽象方法是不能修饰为async

LINQ

  • where
  • Count
  • Any
  • OrderBy
  • Skip/Take
  • Max,Min,Avg,Sum
  • GroupBy
  • Select
  • ToArry/ToList

.NET Core核心基础组件

请添加图片描述

依赖注入

什么是控制反转、服务定位器和依赖注入

控制反转就是把“创建和组装对象”操作的控制权从业务逻辑的new转移到框架中,这样业务代码只要说明我要A对象,框架就会帮助我们创建这个对象

控制反转的两种方式:

  • 服务定位器:调用GetService方法就可以获取想要的对象
  • 依赖注入:框架中有个自动为类的属性赋值的功能,只要代码声明需要什么类型对象,框架就会帮助我们创建这个对象

依赖注入生命周期

  • 瞬态
  • 范围
  • 单例
NuGet包
  • Microsoft.Extensions.DependencyInjection
使用
  1. 创建容器:new ServiceCollection
  2. 注册服务:services.AddXXX()
  3. 调用IServiceCollection的BuildServiceProvider方法创建一个ServiceProvider对象
  4. 调用ServiceProvider类的GetRequiredService()方法

配置系统

json

依赖
  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration
使用方法
  • new ConfigurationBuilder
  • configurationBuilder.AddJsonFile()
  • configurationBuilder.Build()
  • config[“name”]
选项方式读取

IOptions不监听配置的改变,因此它的资源占用比较少,适用于对服务器启动后就不会改变的值进行读取。如果我们需要在程序运行中读取修改后的值,建议使用IOptionsSnapshot

  • 构建模型类
  • 配置读取配置的Demo类private readonly IOptionsSnapshot<DbSettings> optDbSettings;
  • services.AddOptions()
    .Configure<DbSettings>(e => config.GetSection("DB").Bind(e))
    .Configure<SmtpSettings>(e => config.GetSection("Smtp").Bind(e));
    

命令行读取配置

依赖
  • Microsoft.Extensions.Configuration.CommandLine
使用
  • AddCommandLine()

环境变量

依赖
  • Microsoft.Extensions.Configuration.EnvironmentVariables
使用
  • AddEnvironmentVariables

多配置问题

按照“后添加的配置提供程序中的配置覆盖之前的配置”的原则

日志

依赖

  • Microsoft.Extensions.Logging
  • 控制台输出日志 Microsoft.Extensions.Logging.Console

文件日志提供程序NLog

在生产环境中我们需要把日志写入存储介质的方式,比如写入文件。

常用的第三方日志提供程序有Log4NetNLogSerilog。这里推荐使用NLog或者Serilog,因为它们不仅使用简单,而且功能强大。

集中式日志

在集群环境中,如果每台服务器都把日志写入本地的文件中,那么在对日志进行分析的时候,我们就需要逐个打开各台服务器的磁盘中的日志文件,这非常麻烦。因此,在分布式环境下,我们最好采用集中式的日志服务器,各台服务器都把产生的日志写入日志服务器。

推荐使用

  • Exceptionless(C#)
    • Exceptionless的开发人员主推它们的日志云服务,也就是不用自己搭建服务器,而是直接购买他们的云服务,然后程序直接把日志发送给他们的服务器即可,因此在Exceptionless的官网是看不到自己部署服务器的页面的,需要到它的GitHub开源页面去找文档的“Self Hosting”这一节。
  • ELK(JAVA)

EF Core基础

请添加图片描述

OnConfiguring

连接配置

OnModelCreating

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}

CRUD

实体类配置

EF Core采用了“约定大于配置”的设计原则,也就是说EF Core会默认按照约定根据实体类以及DbContext的定义来实现和数据库表的映射配置,除非用户显式地指定了配置规则。

配置类

使用:

  • 配置类继承 IEntityTypeConfiguration
1  class BookEntityConfig : IEntityTypeConfiguration<Book>
2  {
3      public void Configure(EntityTypeBuilder<Book> builder)
4      {
5          builder.ToTable("T_Books");
6          builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
7          builder.Property(e => e.AuthorName).HasMaxLength(20).IsRequired();
8      }
9  }

Data Annotation配置

Fluent API配置

注意

  • 推荐Fluent API配置
  • 两种配置都用,Fluent API配置优先级高

关系配置

一对多、一对一、多对多

EF Core中实体类之间关系的配置采用如下的模式:HasXXX(…).WithYYY(…);

关联数据的获取

Include

关系的外键属性的配置

在关系配置中通过HasForeignKey()指定这个属性为外键可以不适用Include

单项导航属性

有时候我们不方便声明双向导航。比如在大部分系统中,基础的“用户”实体类会被非常多的其他实体类引用,这种单向导航属性的配置其实很简单,只要在WithMany方法中不指定属性即可

主键类型

自增long类型

优点

  • 使用简单
  • 所有主流数据库系统都内置了对自增列的支持
  • 新插入的数据会由数据库自动赋予一个新增的、不重复的主键值
  • 自增long类型占用磁盘空间小,可读性强

缺点:

  • 自增long类型的主键在数据库迁移以及分布式系统(如分库分表、数据库集群)中使用起来比较麻烦,而且在高并发插入的时候性能比较差。
Guid算法

Guid算法使用网卡的MAC地址、时间戳等信息生成一个全球唯一的ID。由于Guid的全球唯一性,它适用于分布式系统,在进行多数据库数据合并的时候很方便,因此我们也可以用Guid类型作为主键。

注:

  • 如果我们使用Guid类型(也就是uniqueidentifier类型)作为主键,一定不能把主键设置为聚集索引
  • 在MySQL中,如果使用InnoDB引擎,并且数据插入频繁,那么一定不要用Guid类型作为主键
  • 使用其他数据库管理系统的时候,也请先查阅在对应的数据库管理系统中,是否可以把主键设置为非聚集索引

数据迁移

迁移文件

  • XXX.cs:记录的是和具体数据库无关的抽象模型
  • XXX.Designer.cs:记录的是和具体数据库相关的代码

查看EFCore生成的SQL语句

我们只要在上下文的OnConfiguring方法中调用optionsBuilder类的LogTo方法,传递一个参数为String的委托即可。当相关日志输出的时候,对应的委托就会被执行

原理

IQueryable与IEnumerable

Enumerable类中定义的供普通集合用的Where等方法都是“客户端评估”,Queryable中定义的Where方法都是“服务的评估”

总结:

在使用EF Core的时候,为了避免“客户端评估”,我们要尽量调用IQueryable版本的方法,而不是直接调用IEnumerable版本的方法。

IQueryable的延迟执行

对于IQueryable接口,调用“非立即执行”方法的时候不会执行查询,而调用“立即执行”方法的时候则会立即执行查询。

判断方法:

一个方法是否是立即执行方法的简单方式是:一个方法的返回值类型如果是IQueryable类型,这个方法一般就是非立即执行方法,否则这个方法就是立即执行方法。

注:IQueryable是一个待查询的逻辑,因此它是可以被重复使用的

IQueryable的底层运行

IQueryable是用类似DataReader的方式读取查询结果的。DataReader会分批从数据库服务器读取数据。
优点是客户端内存占用小,缺点是如果遍历读取数据并进行处理的过程缓慢的话,会导致程序占用数据库连接的时间较长,从而降低数据库服务器的并发连接能力。因此,在遍历IQueryable的过程中,它需要占用一个数据库连接。

EF优化

AsNoTracking

如果开发人员能够确认通过上下文查询出来的对象只是用来展示,不会发生状态改变,那么可以使用AsNoTracking方法告诉IQueryable在查询的时候“禁用跟踪”

1  Book[] books = ctx.Books.AsNoTracking().Take(3).ToArray();
2  Book b1 = books[0];
3  b1.Title = "abc";
4  EntityEntry entry1 = ctx.Entry(b1);
5  Console.WriteLine(entry1.State);

上面代码的执行结果是“Detached”,也就说使用AsNoTracking查询出来的实体类是不被上下文跟踪的。

实体类状态跟踪

ctx.Entry(b1).State

并发控制

EF Core内置了使用并发令牌列实现的乐观并发控制,并发令牌列通常就是被并发操作影响的列。

例子:

我们可以把Owner列用作并发令牌列。在更新Owner列的时候,我们把Owner列更新前的值也放入Update语句的条件中,SQL语句如下:Update T_Houses set Owner=新值where Id=1 and Owner=旧值。

使用:

EF Core中,我们只要把被并发修改的属性使用IsConcurrencyToken设置为并发令牌即可。

表达式树

表达式树(expression tree)是用树形数据结构来表示代码逻辑运算的技术,它让我们可以在运行时访问逻辑运算的结构。表达式树在.NET中对应Expression <> 类型。

通过代码动态构建表达式树

  1. 安装NuGet包ExpressionTreeToString
  2. 在代码中添加对ExpressionTreeToString命名空间的引用
  3. 我们就可以在Expression类型上调用ToString扩展方法来输出表达式树结构的字符串了
  4. ExpressionTreeToString提供的ToString(“Object notation”,“C#”)方法只是输出一个用C#语法描述表达式树的结构及每个节点的字符串,但是这个字符串并不是可以直接运行的C#代码。
  5. 我们可以用C#的using static方法来静态引入Expression类
  6. 这样上面的代码就几乎可以直接放到C#代码中编译通过了

注:一般只有在编写不特定于某个实体类的通用框架的时候,由于无法在编译期确定要操作的类名、属性等,才需要编写动态构建表达式树的代码,否则为了提高代码的可读性和可维护性,我们要尽量避免动态构建表达式树。

ASP.NET Core基础组件

请添加图片描述

依赖注入

使用

  1. 注入代码在Program.cs文件中的var app=builder.Build()代码之前
  2. 使用builder.Services.AddXXX<xxxService>()
  3. 直接可在构造方法中注入服务

模块化的服务注入框架

  • 依赖:Zack.Commons
  • 创建Zack.Commons中的IModuleInitializer接口的实现类ModuleInitializer
  • Program.cs添加
    var services = new ServiceCollection();
    // 获取所有的用户程序集
    var assemblies = ReflectionHelper.GetAllReferencedAssemblies();
    // 扫描指定程序集中所有实现了IModuleInitialier接口的类
    services.RunModuleInitializers(assemblies);
    using var sp = services.BuildServiceProvider();
    var items = sp.GetServices<IMyService>();
    foreach (var item in items)
    {
        item.SayHello();
    }
    

EF Core与ASP.NET Core的集成

使用

  • 我们尽量把上下文的数据库配置的代码写到ASP.NET Core项目中,也就是context只编写OnModelCreating方法
  • Program.cs中添加
    builder.Services.AddDbContext<MyDbContext>(opt=>{
        var conStr = builder.Configuration.GetConnectionString("dEFAULT");
        opt.UseMysql(conStr)
    });
    

缓存

客户端响应缓存

使用:
controller添加[ResponseCache(Duration=60)]

服务端响应缓存

使用:
Program.csapp.MapControllers之前加上app.UseResponseCaching

内存缓存

使用:

  1. Program.csbuilder.Build之前添加builder.Services.AddMemoryCache来把内存缓存相关服务注册到依赖注入容器中。
    var items = await memCache.GetOrCreateAsync("AllBooks", async (e) => {
    e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
    logger.LogInformation("从数据库中读取数据");
    return await dbCtx.Books.ToArrayAsync();
    });

过期策略有“绝对过期时间”和“滑动过期时间”两种

缓存穿透问题的规避

如果有恶意访问者使用不存在的图书ID来发送大量的请求,这样的请求就会一直执行第8行查询数据库的代码,因此数据库就会承受非常大的压力,甚至可能会导致数据库服务器崩溃,这种问题就叫作缓存穿透。

解决:在日常开发中只要使用GetOrCreateAsync方法即可,因为这个方法会把null也当成合法的缓存值,这样就可以轻松规避缓存穿透的问题了

缓存雪崩问题的规避

如果数据缓存设置的过期时间都相同,到了过期时间的时候,缓存项会集中过期,因此又会导致大量的数据库请求,这样数据库服务器就会出现周期性的压力,这种陡增的压力甚至会把数据库服务器“压垮”(崩溃),当数据库服务器从崩溃中恢复后,这些压力又压了过来,从而造成数据库服务器反复崩溃、恢复,这就是数据库服务器的“雪崩”。

解决:写缓存时,在基础过期时间之上,再加一个随机的过期时间

缓存数据混乱的规避

使用UserInfo当缓存键,就会存在数据混乱问题

解决: 使用UserInfo+UserId使得缓存键唯一

注意事项

IQueryable、IEnumerable等类型可能存在延迟加载的问题,如果把这两种类型的变量指向的对象保存到内存缓存中,在把它们取出来再去执行的时候,如果它们延迟加载时需要的对象已经被释放,就会执行失败。

因此,这两种类型的变量指向的对象在保存到内存缓存之前,最好将其转换为数组或者List类型,从而强制数据立即加载。

分布式缓存

Redis

.NET Core中提供了统一的分布式缓存服务器的操作接口IDistributedCache,无论用什么类型的分布式缓存服务器,我们都可以统一使用IDistributedCache接口进行操作。

使用:

  1. 依赖:Microsoft.Extensions.Caching.StackExchangeRedis
  2. Program.csbuild之前
    builder.Service.AddStackExchangeRedisCache(options=>{
        options.Configuration="localhost";
        // 前缀,避免和其他数据混淆
        options.InstanceName="lyy_";
    });
    

筛选器

ASP.N ET Core中的筛选器有以下5种类型:授权筛选器、资源筛选器、操作筛选器、异常筛选器和结果筛选器。

异常筛选器

  • 继承IAsyncExceptionFilter
  • Program.cs添加
    builder.Services.Configure<MvcOptions>(options=>{
        option.Filters.Add<MyExceptionFilter>();
    });
    
  • 注意:只有ASP.NET Core线程中的未处理异常才会被异常筛选器处理,后台线程中的异常不会被异常筛选器处理

操作筛选器

  • 继承IAsyncActionFilter
  • 实现OnActionExecutionAsync方法,其中,context参数代表Action执行的上下文对象,从context中我们可以获取请求的路径、参数值等信息;next参数代表下一个要执行的操作筛选器。
  • 一个项目中可以注册多个操作筛选器,这些操作筛选器组成一个链,上一个筛选器执行完了再执行下一个。

中间件

中间件(middleware)是ASP.NET Core中的核心组件,ASP.NET Core MVC框架、响应缓存、用户身份验证、CORS、Swagger等重要的框架功能都是由ASP.NET内置的中间件提供的,我们也可以开发自定义的中间件来提供额外的功能。

每个中间件由前逻辑、next、后逻辑3部分组成,前逻辑为第一段要执行的逻辑代码,next为指向下一个中间件的调用,后逻辑为从下一个中间件返回所执行的逻辑代码。

要进行中间件的开发,我们需要先了解3个重要的概念:Map、Use和Run。Map用来定义一个管道可以处理哪些请求,Use和Run用来定义管道,一个管道由若干个Use和一个Run组成,每个Use引入一个中间件,而Run用来执行最终的核心应用逻辑。如下图所示。

请添加图片描述

中间件使用

  1. Program添加

    app.Map("/test", async appbuiler =>
    {
        appbuiler.Use(async (context, next) =>
        {
            context.Response.ContentType = "text/html";
            await context.Response.WriteAsync("1 Start <br/>");
            await next.Invoke();
            await context.Response.WriteAsync("1 End<br/>");
        });
        appbuiler.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("2 Start <br/>");
            await next.Invoke();
            await context.Response.WriteAsync("2 End<br/>");
        });
        appbuiler.Run(async ctx =>
        {
            await ctx.Response.WriteAsync("hello middleware <br/>");
        });
    });
    

自定义中间件

  • 中间件类是一个普通的.NET类,它不需要继承任何父类或者实现任何接口

  • 但是这个类需要有一个构造方法,构造方法至少要有一个RequestDelegate类型的参数,这个参数用来指向下一个中间件。

  • 这个类还需要定义一个名字为Invoke或InvokeAsync的方法,方法中至少有一个HttpContext类型的参数,方法的返回值必须是Task类型。中间件类的构造方法和Invoke(或InvokeAsync)方法还可以定义其他参数,其他参数会通过依赖注入自动赋值。

  • Program中使用appbuilder.UseMiddleware<Class>()调用中间件

注意

  • 中间件的组装顺序非常重要,在使用它们的时候一定要注意仔细阅读文档中关于中间件组装顺序的说明

高级组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o968hdaS-1665807763193)(./高级组件.png)]

Authentication与Authorization

标识框架使用EF Core对数据库进行操作,由于EF Core屏蔽了底层数据库的差异,因此标识框架支持几乎所有数据库。

标识框架中提供了IdentityUser<TKey>、IdentityRole<TKey>两个实体类型,我们一般编写继承自IdentityUser、IdentityRole等的自定义类。

使用:

  1. NuGet安装Microsoft.AspNetCore.Identity.EntityFrameworkCore

  2. 编写分别继承自IdentityUser<long>、IdentityRole<long>的User类和Role类

  3. 创建继承自IdentityDbContext的类,这是一个EF Core中的上下文类,我们可以通过这个类操作数据库。IdentityDbContext是一个泛型类,有3个泛型参数,分别代表用户类型、角色类型和主键类型。

  4. 向依赖注入容器中注册与标识框架相关的服务,并且对相关的选项进行配置。

    services.AddDbContext<IdDbContext>(opt => {
    string connStr = builder.Configuration.GetConnectionString("Default");
    opt.UseSqlServer(connStr);
    });
    services.AddDataProtection();
    services.AddIdentityCore<User>(options =>
    {
        options.Password.RequireDigit = false;
        options.Password.RequireLowercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = false;
        options.Password.RequiredLength = 6;
        options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
        options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
    });
    var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services);
    idBuilder.AddEntityFrameworkStores<IdDbContext>()
        .AddDefaultTokenProviders()
        .AddRoleManager<RoleManager<Role>>()
        .AddUserManager<UserManager<User>>();
    
  5. 通过执行Add-Migration、Update-database等命令执行EF Core的数据库迁移

  6. 编写控制器的代码。我们在控制器中需要对角色、用户进行操作

    private readonly RoleManager<Role> roleManager;
    private readonly UserManager<User> userManager;
    

除了这些基本的用法之外,标识框架中还提供了多因素验证(短信验证、指纹验证等)、外部登录、重置密码等功能,官方文档中关于这些内容的介绍非常清晰。

JWT

Session:

  • 实现用户登录功能的经典做法是用Session

  • 也就是在用户登录验证成功后,服务器端生成唯一标识SessionId

  • 服务器端不仅会把SessionId返回给浏览器端,还会把SessionId和登录用户的信息的对应关系保存到服务器的内存中

  • 当浏览器端再次向服务器端发送请求的时候,浏览器端就在HTTP请求中携带SessionId,服务器端就可以根据SessionId从服务器的内存中取到用户的信息,这样就实现了用户登录的功能。

但是在分布式环境下,特别是在“前后端分离、多客户端”时代,Session暴露出很多缺点。

  • 当登录用户量很大的时候,Session数据就会占用非常多的内存
  • 而且无法支持分布式集群环境
  • 如果Session数据保存到Redis等状态服务器中,它可以支持分布式集群环境,但是每遇到一次客户端请求都要向状态服务器获取一次Session数据,这会导致请求的响应速度变慢

在现在的项目开发中,我们倾向于采用JWT代替Session实现登录。JWT全称是JSON web token,从名字中可以看出,JWT是使用JSON格式来保存令牌信息的。

  • 为了防止客户端的数据造假,保存在客户端的令牌经过了签名处理,而签名的密钥只有服务器端才知道,每次服务器端收到客户端提交的令牌的时候都要检查一下签名,如果发现数据被篡改,则拒绝接收客户端提交的令牌。

JWT的结构如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ny6saB8z-1665807763194)(./JWT%E7%BB%93%E6%9E%84%E5%9B%BE.png)]

JWT的头部(header)中保存的是加密算法的说明,负载(payload)中保存的是用户的ID、用户名、角色等信息,签名(signature)是根据头部和负载一起算出来的值。

JWT登录流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WyW0Ccqb-1665807763194)(./JWT%E7%99%BB%E5%BD%95%E6%B5%81%E7%A8%8B.png)]

使用:

  1. 安装NuGet包System.IdentityModel.Tokens.Jwt
  2. 编写生产JWT的程序
    1  var claims = new List<Claim>();
    2  claims.Add(new Claim(ClaimTypes.NameIdentifier, "6"));
    3  claims.Add(new Claim(ClaimTypes.Name, "yzk"));
    4  claims.Add(new Claim(ClaimTypes.Role, "User"));
    5  claims.Add(new Claim(ClaimTypes.Role, "Admin"));
    6  claims.Add(new Claim("PassPort", "E90000082"));
    7  string key = "fasdfad&9045dafz222#fadpio@0232";
    8  DateTime expires = DateTime.Now.AddDays(1);
    9  byte[] secBytes = Encoding.UTF8.GetBytes(key);
    10 var secKey = new SymmetricSecurityKey(secBytes);
    11 var credentials = new SigningCredentials(secKey,SecurityAlgorithms.HmacSha256Signature);
    12 var tokenDescriptor = new JwtSecurityToken(claims: claims,
    13     expires: expires, signingCredentials: credentials);
    14 string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    15 Console.WriteLine(jwt);
    
  3. 调用JwtSecurityTokenHandler类对JWT进行解码,因为它会在对JWT解码前对签名进行校验
    1  string jwt = Console.ReadLine()!;
    2  string secKey = "fasdfad&9045dafz222#fadpio@0232";
    3  JwtSecurityTokenHandler tokenHandler = new();
    4  TokenValidationParameters valParam = new ();
    5  var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secKey));
    6  valParam.IssuerSigningKey = securityKey;
    7  valParam.ValidateIssuer = false;
    8  valParam.ValidateAudience = false;
    9  ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwt,
    10         valParam,out SecurityToken secToken);
    11 foreach (var claim in claimsPrincipal.Claims)
    12 {
    13     Console.WriteLine($"{claim.Type}={claim.Value}");
    14 }
    

注意:

  • 一定不要把不能被客户端知道的信息放到负载中。
  • 当我们使用这个被篡改的JWT去运行代码8-11,程序运行时就会抛出内容为“Signature validation failed”的异常。

NET Core对于JWT的封装

  1. 当我们使用这个被篡改的JWT去运行代码8-11,程序运行时就会抛出内容为“Signature validation failed”的异常。
  2. NuGet为项目安装Microsoft.AspNetCore.Authentication.JwtBearer包
  3. 编写代码对JWT进行配置
    1  services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
    2  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    3  .AddJwtBearer(x =>
    4  {
    5     var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
    6     byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
    7     var secKey = new SymmetricSecurityKey(keyBytes);
    8     x.TokenValidationParameters = new()
    9     {
    10       ValidateIssuer=false, ValidateAudience=false, ValidateLifetime=true,
    11       ValidateIssuerSigningKey=true, IssuerSigningKey=secKey
    12    };
    13 });
    
  4. app.UseAuthorization之前添加app.UseAuthentication
  5. 类似上面生产JWT
  6. 在需要登录才能访问的控制器类上添加[Authorize]这个ASP.NET Core内置的Attribute

注:

  • 在前端项目中,我们可以把令牌保存到Cookie、LocalStorage等位置,从而在后续请求中重复使用
  • 而对于移动App、PC客户端,我们可以把令牌保存到配置文件中或者本地文件数据库中。

让Swagger中调试带验证的请求更简单

修改AddSwaggerGen()

1  builder.Services.AddSwaggerGen(c =>
2  {
3      var scheme = new OpenApiSecurityScheme()
4      {
5          Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'",
6          Reference = new OpenApiReference{Type = ReferenceType.SecurityScheme,
7              Id = "Authorization"},
8          Scheme = "oauth2",Name = "Authorization",
9          In = ParameterLocation.Header,Type = SecuritySchemeType.ApiKey,
10     };
11     c.AddSecurityDefinition("Authorization", scheme);
12     var requirement = new OpenApiSecurityRequirement();
13     requirement[scheme] = new List<string>();
14     c.AddSecurityRequirement(requirement);
15 });

解决JWT无法提前撤回的难题

JWT的缺点是:一旦JWT被发放给客户端,在有效期内这个令牌就一直有效,令牌是无法被提前撤回的。

解决思路是:在用户表中增加一个整数类型的列JWTVersion,它代表最后一次发放出去的令牌的版本号;每次登录、发放令牌的时候,我们都让JWTVersion的值自增,同时将JWTVersion的值也放到JWT的负载中;当执行禁用用户、撤回用户的令牌等操作的时候,我们让这个用户对应的JWTVersion的值自增;当服务器端收到客户端提交的JWT后,先把JWT中的JWTVersion值和数据库中的JWTVersion值做比较,如果JWT中JWTVersion的值小于数据库中JWTVersion的值,就说明这个JWT过期了,这样我们就实现了JWT的撤回机制。

JWT和Session比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fNlSjU4p-1665807763194)(./JWT%E5%92%8CSession%E6%AF%94%E8%BE%83.png)]

托管服务

使用:

  1. 继承BackgroudService

托管服务中使用依赖注入的陷阱

  • 托管服务是以单例的生命周期注册到依赖注入容器中的。

  • 长生命周期的服务不能依赖短生命周期的服务,因此我们可以在托管服务中通过构造方法注入其他生命周期为单例的服务,但是不能注入生命周期为范围或者瞬态的服务。

  • 我们可以通过构造方法注入IServiceScopeFactory服务,它可以用来创建IServiceScope对象,这样我们就可以通过IServiceScope来创建短生命周期的服务了

    this.serviceScope = scopeFactory.CreateScope();
    var sp = serviceScope.ServiceProvider;
    this.ctx = sp.GetRequiredService<TestDbContext>();
    
  1. services.AddHostedService()
  2. 由于IServiceScope继承了IDisposable接口,因此我们需要在托管服务的Dispose方法中销毁serviceScope。

请求数据校验

FluentValidation的基本使用

  1. 在项目中安装NuGet包FluentValidation.AspNetCore。
  2. 在Program.cs中添加注册相关服务的代码
     1  builder.Services.AddFluentValidation(fv => {
     2      Assembly assembly = Assembly.GetExecutingAssembly();
     3      fv.RegisterValidatorsFromAssembly(assembly);
     4  });
    
  3. 编写一个模型类Request
  4. 编写一个继承自AbstractValidator的数据校验类
     1  public class Login2RequestValidator: AbstractValidator<Login2Request>
     2  {
     3     public Login2RequestValidator()
     4     {
     5        RuleFor(x=>x.Email).NotNull().EmailAddress()
     6           .Must(v=>v.EndsWith("@qq.com")||v.EndsWith("@163.com"))
     7           .WithMessage("只支持QQ和163邮箱");
     8        RuleFor(x => x.Password).NotNull().Length(3, 10)
     9           .WithMessage("密码长度必须介于3到10之间")
     10          .Equal(x => x.Password2).WithMessage("两次密码必须一致");
     11    }
     12 }
    

SignalR

ASP.NET Core SignalR(以下简称SignalR)是.NET Core平台中对WebSocket的封装,从而让开发人员可以更简单地进行WebSocket开发。

基本使用:

  1. ASP.NET Core SignalR(以下简称SignalR)是.NET Core平台中对WebSocket的封装,从而让开发人员可以更简单地进行WebSocket开发。
  2. 编写方法
  3. 编辑Program.cs,在builder.Build之前调用builder.Services.AddSignalR注册所有SignalR的服务,在app.MapControllers之前调用app.MapHub(“/Hubs/ChatRoomHub”)启用SignalR中间件,并且设置当客户端通过SignalR请求“/Hubs/ChatRoomHub”这个路径的时候,由ChatRoomHub进行处理。

从中心调用客户端方法

await connection.InvokeAsync("SendMessage", 
    userTextBox.Text, messageTextBox.Text);

从客户端调用中心方法

connection.On<string, string>("ReceiveMessage", (user, message) =>
{
    this.Dispatcher.Invoke(() =>
    {
       var newMessage = $"{user}: {message}";
       messagesList.Items.Add(newMessage);
    });
});

部署

我们在开发环境中运行的项目所加载的程序集是为了方便开发工具调试而生成的调试版程序集,运行效率并不高,因此我们不能直接把项目文件夹下bin/Debug中的程序集部署到生产环境的服务器上。我们应该创建网站的发布版,创建网站发布版的过程简称为“发布”。

两种部署模式:“框架独立”和“独立”

新语法

顶级语句

可以直接在C#文件中编写入口代码,不再需要声明类和方法

全局using

建立Usings.cs全局同意管理引用,使用global using System.Text.Json;引用

simply using embeding

using会在程序结束关闭,或者使用大括号,括号结束using资源释放

using (var conn = new SqlConnection(conStr))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "select * from form";
        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {

            }
        }
    }
    
}
           ||
           ||
           ||
           ||
           ||
           \/
using var con = new SqlConnection(connStr);
con.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "select * from form";
using var reader = cmd.ExecuteReader();
while (reader.Read())
{

}

simply namespace to avoid embed

直接写namespace关键字避免命名空间嵌套,代码结构嵌套严重较难看

namespace MyNamespace;

Record

使用Record可以创建类,会重写ToString,Equals;可以使用with快速构建Record这种特殊类

#region Record
#region demo1
var p1 = new Person("a", "b");
var p2 = new Person("a", "b");
Console.WriteLine(p1);
Console.WriteLine(p1 == p2);
public record Person(string FirstName, string LastName);
#endregion

#region demo2
var u1 = new User("li", 25);
var u2 = new User("li", "56464654@qq.com", 18);
var u3 = u1 with { Email = "test_fjiort@qq.com", Age = 99 };
public record User(string UserName, string? Email, int Age)
{
    public User(string userName, int age) : this(userName, null, age)
    {

    }
}

#endregion
#endregion

异步编程

异步编程提高服务器接待请求的数量,但不会使得单个请求处理效率变高,甚至可能略又降低

await\async

使用异步方法注意点:

  • 返回值Task<T>
  • 方法一般以Async结尾
  • 调用一般在方法签名加上await

原理:

使用反编译工具查看代码

async方法会被C#编译器编译成一个类,并根据await调用把方法切分成多个状态,对async方法的调用就会被拆分成若干次对MoveNext方法的调用

线程切换

在异步方法进行await调用的等待期间,框架会把当前的线程返回给线程池,等异步方法调用执行完毕,框架会从线程池再去除一个线程,以执行后续的代码。

注意

  • 异步方法不等于多线程。
    • 如何使得代码再新线程执行呢,使用Task.Run
  • 异步方法不一定必须有async。
    • 如果一个异步方法只是对别的异步方法进行简单的调用,并没有复杂的逻辑,比如获取异步方法的返回值后再进一步的处理,就可以去掉asqyn\await关键字
  • 建议开发人员只使用异步方法,因为这样能提升系统并发处理能力
  • 若由于框架限制,我们编写的方法不能标注async
    • 那么可以在Task<T>类型对象调用Result属性来等待异步执行结束获取返回值
    • 返回值为Task,可以在Task类型对象调用Wait方法来调用异步方法并等待任务执行结束
  • 异步暂停使用Thread.Sleep
  • 异步方法中的CancellationToken对象让异步方法提前终止
  • 可以使用Task.WhenAll等待多个Task的执行结束
  • 接口中的方法或者抽象方法是不能修饰为async

LINQ

  • where
  • Count
  • Any
  • OrderBy
  • Skip/Take
  • Max,Min,Avg,Sum
  • GroupBy
  • Select
  • ToArry/ToList

.NET Core核心基础组件

请添加图片描述

依赖注入

什么是控制反转、服务定位器和依赖注入

控制反转就是把“创建和组装对象”操作的控制权从业务逻辑的new转移到框架中,这样业务代码只要说明我要A对象,框架就会帮助我们创建这个对象

控制反转的两种方式:

  • 服务定位器:调用GetService方法就可以获取想要的对象
  • 依赖注入:框架中有个自动为类的属性赋值的功能,只要代码声明需要什么类型对象,框架就会帮助我们创建这个对象

依赖注入生命周期

  • 瞬态
  • 范围
  • 单例
NuGet包
  • Microsoft.Extensions.DependencyInjection
使用
  1. 创建容器:new ServiceCollection
  2. 注册服务:services.AddXXX()
  3. 调用IServiceCollection的BuildServiceProvider方法创建一个ServiceProvider对象
  4. 调用ServiceProvider类的GetRequiredService()方法

配置系统

json

依赖
  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration
使用方法
  • new ConfigurationBuilder
  • configurationBuilder.AddJsonFile()
  • configurationBuilder.Build()
  • config[“name”]
选项方式读取

IOptions不监听配置的改变,因此它的资源占用比较少,适用于对服务器启动后就不会改变的值进行读取。如果我们需要在程序运行中读取修改后的值,建议使用IOptionsSnapshot

  • 构建模型类
  • 配置读取配置的Demo类private readonly IOptionsSnapshot<DbSettings> optDbSettings;
  • services.AddOptions()
    .Configure<DbSettings>(e => config.GetSection("DB").Bind(e))
    .Configure<SmtpSettings>(e => config.GetSection("Smtp").Bind(e));
    

命令行读取配置

依赖
  • Microsoft.Extensions.Configuration.CommandLine
使用
  • AddCommandLine()

环境变量

依赖
  • Microsoft.Extensions.Configuration.EnvironmentVariables
使用
  • AddEnvironmentVariables

多配置问题

按照“后添加的配置提供程序中的配置覆盖之前的配置”的原则

日志

依赖

  • Microsoft.Extensions.Logging
  • 控制台输出日志 Microsoft.Extensions.Logging.Console

文件日志提供程序NLog

在生产环境中我们需要把日志写入存储介质的方式,比如写入文件。

常用的第三方日志提供程序有Log4NetNLogSerilog。这里推荐使用NLog或者Serilog,因为它们不仅使用简单,而且功能强大。

集中式日志

在集群环境中,如果每台服务器都把日志写入本地的文件中,那么在对日志进行分析的时候,我们就需要逐个打开各台服务器的磁盘中的日志文件,这非常麻烦。因此,在分布式环境下,我们最好采用集中式的日志服务器,各台服务器都把产生的日志写入日志服务器。

推荐使用

  • Exceptionless(C#)
    • Exceptionless的开发人员主推它们的日志云服务,也就是不用自己搭建服务器,而是直接购买他们的云服务,然后程序直接把日志发送给他们的服务器即可,因此在Exceptionless的官网是看不到自己部署服务器的页面的,需要到它的GitHub开源页面去找文档的“Self Hosting”这一节。
  • ELK(JAVA)

EF Core基础

请添加图片描述

OnConfiguring

连接配置

OnModelCreating

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}

CRUD

实体类配置

EF Core采用了“约定大于配置”的设计原则,也就是说EF Core会默认按照约定根据实体类以及DbContext的定义来实现和数据库表的映射配置,除非用户显式地指定了配置规则。

配置类

使用:

  • 配置类继承 IEntityTypeConfiguration
1  class BookEntityConfig : IEntityTypeConfiguration<Book>
2  {
3      public void Configure(EntityTypeBuilder<Book> builder)
4      {
5          builder.ToTable("T_Books");
6          builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
7          builder.Property(e => e.AuthorName).HasMaxLength(20).IsRequired();
8      }
9  }

Data Annotation配置

Fluent API配置

注意

  • 推荐Fluent API配置
  • 两种配置都用,Fluent API配置优先级高

关系配置

一对多、一对一、多对多

EF Core中实体类之间关系的配置采用如下的模式:HasXXX(…).WithYYY(…);

关联数据的获取

Include

关系的外键属性的配置

在关系配置中通过HasForeignKey()指定这个属性为外键可以不适用Include

单项导航属性

有时候我们不方便声明双向导航。比如在大部分系统中,基础的“用户”实体类会被非常多的其他实体类引用,这种单向导航属性的配置其实很简单,只要在WithMany方法中不指定属性即可

主键类型

自增long类型

优点

  • 使用简单
  • 所有主流数据库系统都内置了对自增列的支持
  • 新插入的数据会由数据库自动赋予一个新增的、不重复的主键值
  • 自增long类型占用磁盘空间小,可读性强

缺点:

  • 自增long类型的主键在数据库迁移以及分布式系统(如分库分表、数据库集群)中使用起来比较麻烦,而且在高并发插入的时候性能比较差。
Guid算法

Guid算法使用网卡的MAC地址、时间戳等信息生成一个全球唯一的ID。由于Guid的全球唯一性,它适用于分布式系统,在进行多数据库数据合并的时候很方便,因此我们也可以用Guid类型作为主键。

注:

  • 如果我们使用Guid类型(也就是uniqueidentifier类型)作为主键,一定不能把主键设置为聚集索引
  • 在MySQL中,如果使用InnoDB引擎,并且数据插入频繁,那么一定不要用Guid类型作为主键
  • 使用其他数据库管理系统的时候,也请先查阅在对应的数据库管理系统中,是否可以把主键设置为非聚集索引

数据迁移

迁移文件

  • XXX.cs:记录的是和具体数据库无关的抽象模型
  • XXX.Designer.cs:记录的是和具体数据库相关的代码

查看EFCore生成的SQL语句

我们只要在上下文的OnConfiguring方法中调用optionsBuilder类的LogTo方法,传递一个参数为String的委托即可。当相关日志输出的时候,对应的委托就会被执行

原理

IQueryable与IEnumerable

Enumerable类中定义的供普通集合用的Where等方法都是“客户端评估”,Queryable中定义的Where方法都是“服务的评估”

总结:

在使用EF Core的时候,为了避免“客户端评估”,我们要尽量调用IQueryable版本的方法,而不是直接调用IEnumerable版本的方法。

IQueryable的延迟执行

对于IQueryable接口,调用“非立即执行”方法的时候不会执行查询,而调用“立即执行”方法的时候则会立即执行查询。

判断方法:

一个方法是否是立即执行方法的简单方式是:一个方法的返回值类型如果是IQueryable类型,这个方法一般就是非立即执行方法,否则这个方法就是立即执行方法。

注:IQueryable是一个待查询的逻辑,因此它是可以被重复使用的

IQueryable的底层运行

IQueryable是用类似DataReader的方式读取查询结果的。DataReader会分批从数据库服务器读取数据。
优点是客户端内存占用小,缺点是如果遍历读取数据并进行处理的过程缓慢的话,会导致程序占用数据库连接的时间较长,从而降低数据库服务器的并发连接能力。因此,在遍历IQueryable的过程中,它需要占用一个数据库连接。

EF优化

AsNoTracking

如果开发人员能够确认通过上下文查询出来的对象只是用来展示,不会发生状态改变,那么可以使用AsNoTracking方法告诉IQueryable在查询的时候“禁用跟踪”

1  Book[] books = ctx.Books.AsNoTracking().Take(3).ToArray();
2  Book b1 = books[0];
3  b1.Title = "abc";
4  EntityEntry entry1 = ctx.Entry(b1);
5  Console.WriteLine(entry1.State);

上面代码的执行结果是“Detached”,也就说使用AsNoTracking查询出来的实体类是不被上下文跟踪的。

实体类状态跟踪

ctx.Entry(b1).State

并发控制

EF Core内置了使用并发令牌列实现的乐观并发控制,并发令牌列通常就是被并发操作影响的列。

例子:

我们可以把Owner列用作并发令牌列。在更新Owner列的时候,我们把Owner列更新前的值也放入Update语句的条件中,SQL语句如下:Update T_Houses set Owner=新值where Id=1 and Owner=旧值。

使用:

EF Core中,我们只要把被并发修改的属性使用IsConcurrencyToken设置为并发令牌即可。

表达式树

表达式树(expression tree)是用树形数据结构来表示代码逻辑运算的技术,它让我们可以在运行时访问逻辑运算的结构。表达式树在.NET中对应Expression <> 类型。

通过代码动态构建表达式树

  1. 安装NuGet包ExpressionTreeToString
  2. 在代码中添加对ExpressionTreeToString命名空间的引用
  3. 我们就可以在Expression类型上调用ToString扩展方法来输出表达式树结构的字符串了
  4. ExpressionTreeToString提供的ToString(“Object notation”,“C#”)方法只是输出一个用C#语法描述表达式树的结构及每个节点的字符串,但是这个字符串并不是可以直接运行的C#代码。
  5. 我们可以用C#的using static方法来静态引入Expression类
  6. 这样上面的代码就几乎可以直接放到C#代码中编译通过了

注:一般只有在编写不特定于某个实体类的通用框架的时候,由于无法在编译期确定要操作的类名、属性等,才需要编写动态构建表达式树的代码,否则为了提高代码的可读性和可维护性,我们要尽量避免动态构建表达式树。

ASP.NET Core基础组件

请添加图片描述

依赖注入

使用

  1. 注入代码在Program.cs文件中的var app=builder.Build()代码之前
  2. 使用builder.Services.AddXXX<xxxService>()
  3. 直接可在构造方法中注入服务

模块化的服务注入框架

  • 依赖:Zack.Commons
  • 创建Zack.Commons中的IModuleInitializer接口的实现类ModuleInitializer
  • Program.cs添加
    var services = new ServiceCollection();
    // 获取所有的用户程序集
    var assemblies = ReflectionHelper.GetAllReferencedAssemblies();
    // 扫描指定程序集中所有实现了IModuleInitialier接口的类
    services.RunModuleInitializers(assemblies);
    using var sp = services.BuildServiceProvider();
    var items = sp.GetServices<IMyService>();
    foreach (var item in items)
    {
        item.SayHello();
    }
    

EF Core与ASP.NET Core的集成

使用

  • 我们尽量把上下文的数据库配置的代码写到ASP.NET Core项目中,也就是context只编写OnModelCreating方法
  • Program.cs中添加
    builder.Services.AddDbContext<MyDbContext>(opt=>{
        var conStr = builder.Configuration.GetConnectionString("dEFAULT");
        opt.UseMysql(conStr)
    });
    

缓存

客户端响应缓存

使用:
controller添加[ResponseCache(Duration=60)]

服务端响应缓存

使用:
Program.csapp.MapControllers之前加上app.UseResponseCaching

内存缓存

使用:

  1. Program.csbuilder.Build之前添加builder.Services.AddMemoryCache来把内存缓存相关服务注册到依赖注入容器中。
    var items = await memCache.GetOrCreateAsync("AllBooks", async (e) => {
    e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
    logger.LogInformation("从数据库中读取数据");
    return await dbCtx.Books.ToArrayAsync();
    });

过期策略有“绝对过期时间”和“滑动过期时间”两种

缓存穿透问题的规避

如果有恶意访问者使用不存在的图书ID来发送大量的请求,这样的请求就会一直执行第8行查询数据库的代码,因此数据库就会承受非常大的压力,甚至可能会导致数据库服务器崩溃,这种问题就叫作缓存穿透。

解决:在日常开发中只要使用GetOrCreateAsync方法即可,因为这个方法会把null也当成合法的缓存值,这样就可以轻松规避缓存穿透的问题了

缓存雪崩问题的规避

如果数据缓存设置的过期时间都相同,到了过期时间的时候,缓存项会集中过期,因此又会导致大量的数据库请求,这样数据库服务器就会出现周期性的压力,这种陡增的压力甚至会把数据库服务器“压垮”(崩溃),当数据库服务器从崩溃中恢复后,这些压力又压了过来,从而造成数据库服务器反复崩溃、恢复,这就是数据库服务器的“雪崩”。

解决:写缓存时,在基础过期时间之上,再加一个随机的过期时间

缓存数据混乱的规避

使用UserInfo当缓存键,就会存在数据混乱问题

解决: 使用UserInfo+UserId使得缓存键唯一

注意事项

IQueryable、IEnumerable等类型可能存在延迟加载的问题,如果把这两种类型的变量指向的对象保存到内存缓存中,在把它们取出来再去执行的时候,如果它们延迟加载时需要的对象已经被释放,就会执行失败。

因此,这两种类型的变量指向的对象在保存到内存缓存之前,最好将其转换为数组或者List类型,从而强制数据立即加载。

分布式缓存

Redis

.NET Core中提供了统一的分布式缓存服务器的操作接口IDistributedCache,无论用什么类型的分布式缓存服务器,我们都可以统一使用IDistributedCache接口进行操作。

使用:

  1. 依赖:Microsoft.Extensions.Caching.StackExchangeRedis
  2. Program.csbuild之前
    builder.Service.AddStackExchangeRedisCache(options=>{
        options.Configuration="localhost";
        // 前缀,避免和其他数据混淆
        options.InstanceName="lyy_";
    });
    

筛选器

ASP.N ET Core中的筛选器有以下5种类型:授权筛选器、资源筛选器、操作筛选器、异常筛选器和结果筛选器。

异常筛选器

  • 继承IAsyncExceptionFilter
  • Program.cs添加
    builder.Services.Configure<MvcOptions>(options=>{
        option.Filters.Add<MyExceptionFilter>();
    });
    
  • 注意:只有ASP.NET Core线程中的未处理异常才会被异常筛选器处理,后台线程中的异常不会被异常筛选器处理

操作筛选器

  • 继承IAsyncActionFilter
  • 实现OnActionExecutionAsync方法,其中,context参数代表Action执行的上下文对象,从context中我们可以获取请求的路径、参数值等信息;next参数代表下一个要执行的操作筛选器。
  • 一个项目中可以注册多个操作筛选器,这些操作筛选器组成一个链,上一个筛选器执行完了再执行下一个。

中间件

中间件(middleware)是ASP.NET Core中的核心组件,ASP.NET Core MVC框架、响应缓存、用户身份验证、CORS、Swagger等重要的框架功能都是由ASP.NET内置的中间件提供的,我们也可以开发自定义的中间件来提供额外的功能。

每个中间件由前逻辑、next、后逻辑3部分组成,前逻辑为第一段要执行的逻辑代码,next为指向下一个中间件的调用,后逻辑为从下一个中间件返回所执行的逻辑代码。

要进行中间件的开发,我们需要先了解3个重要的概念:Map、Use和Run。Map用来定义一个管道可以处理哪些请求,Use和Run用来定义管道,一个管道由若干个Use和一个Run组成,每个Use引入一个中间件,而Run用来执行最终的核心应用逻辑。

中间件使用

  1. Program添加

    app.Map("/test", async appbuiler =>
    {
        appbuiler.Use(async (context, next) =>
        {
            context.Response.ContentType = "text/html";
            await context.Response.WriteAsync("1 Start <br/>");
            await next.Invoke();
            await context.Response.WriteAsync("1 End<br/>");
        });
        appbuiler.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("2 Start <br/>");
            await next.Invoke();
            await context.Response.WriteAsync("2 End<br/>");
        });
        appbuiler.Run(async ctx =>
        {
            await ctx.Response.WriteAsync("hello middleware <br/>");
        });
    });
    

自定义中间件

  • 中间件类是一个普通的.NET类,它不需要继承任何父类或者实现任何接口

  • 但是这个类需要有一个构造方法,构造方法至少要有一个RequestDelegate类型的参数,这个参数用来指向下一个中间件。

  • 这个类还需要定义一个名字为Invoke或InvokeAsync的方法,方法中至少有一个HttpContext类型的参数,方法的返回值必须是Task类型。中间件类的构造方法和Invoke(或InvokeAsync)方法还可以定义其他参数,其他参数会通过依赖注入自动赋值。

  • Program中使用appbuilder.UseMiddleware<Class>()调用中间件

注意

  • 中间件的组装顺序非常重要,在使用它们的时候一定要注意仔细阅读文档中关于中间件组装顺序的说明

高级组件

请添加图片描述

Authentication与Authorization

标识框架使用EF Core对数据库进行操作,由于EF Core屏蔽了底层数据库的差异,因此标识框架支持几乎所有数据库。

标识框架中提供了IdentityUser<TKey>、IdentityRole<TKey>两个实体类型,我们一般编写继承自IdentityUser、IdentityRole等的自定义类。

使用:

  1. NuGet安装Microsoft.AspNetCore.Identity.EntityFrameworkCore

  2. 编写分别继承自IdentityUser<long>、IdentityRole<long>的User类和Role类

  3. 创建继承自IdentityDbContext的类,这是一个EF Core中的上下文类,我们可以通过这个类操作数据库。IdentityDbContext是一个泛型类,有3个泛型参数,分别代表用户类型、角色类型和主键类型。

  4. 向依赖注入容器中注册与标识框架相关的服务,并且对相关的选项进行配置。

    services.AddDbContext<IdDbContext>(opt => {
    string connStr = builder.Configuration.GetConnectionString("Default");
    opt.UseSqlServer(connStr);
    });
    services.AddDataProtection();
    services.AddIdentityCore<User>(options =>
    {
        options.Password.RequireDigit = false;
        options.Password.RequireLowercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = false;
        options.Password.RequiredLength = 6;
        options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
        options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
    });
    var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services);
    idBuilder.AddEntityFrameworkStores<IdDbContext>()
        .AddDefaultTokenProviders()
        .AddRoleManager<RoleManager<Role>>()
        .AddUserManager<UserManager<User>>();
    
  5. 通过执行Add-Migration、Update-database等命令执行EF Core的数据库迁移

  6. 编写控制器的代码。我们在控制器中需要对角色、用户进行操作

    private readonly RoleManager<Role> roleManager;
    private readonly UserManager<User> userManager;
    

除了这些基本的用法之外,标识框架中还提供了多因素验证(短信验证、指纹验证等)、外部登录、重置密码等功能,官方文档中关于这些内容的介绍非常清晰。

JWT

Session:

  • 实现用户登录功能的经典做法是用Session

  • 也就是在用户登录验证成功后,服务器端生成唯一标识SessionId

  • 服务器端不仅会把SessionId返回给浏览器端,还会把SessionId和登录用户的信息的对应关系保存到服务器的内存中

  • 当浏览器端再次向服务器端发送请求的时候,浏览器端就在HTTP请求中携带SessionId,服务器端就可以根据SessionId从服务器的内存中取到用户的信息,这样就实现了用户登录的功能。

但是在分布式环境下,特别是在“前后端分离、多客户端”时代,Session暴露出很多缺点。

  • 当登录用户量很大的时候,Session数据就会占用非常多的内存
  • 而且无法支持分布式集群环境
  • 如果Session数据保存到Redis等状态服务器中,它可以支持分布式集群环境,但是每遇到一次客户端请求都要向状态服务器获取一次Session数据,这会导致请求的响应速度变慢

在现在的项目开发中,我们倾向于采用JWT代替Session实现登录。JWT全称是JSON web token,从名字中可以看出,JWT是使用JSON格式来保存令牌信息的。

  • 为了防止客户端的数据造假,保存在客户端的令牌经过了签名处理,而签名的密钥只有服务器端才知道,每次服务器端收到客户端提交的令牌的时候都要检查一下签名,如果发现数据被篡改,则拒绝接收客户端提交的令牌。

JWT的结构如图

请添加图片描述

JWT的头部(header)中保存的是加密算法的说明,负载(payload)中保存的是用户的ID、用户名、角色等信息,签名(signature)是根据头部和负载一起算出来的值。

JWT登录流程
请添加图片描述

使用:

  1. 安装NuGet包System.IdentityModel.Tokens.Jwt
  2. 编写生产JWT的程序
    1  var claims = new List<Claim>();
    2  claims.Add(new Claim(ClaimTypes.NameIdentifier, "6"));
    3  claims.Add(new Claim(ClaimTypes.Name, "yzk"));
    4  claims.Add(new Claim(ClaimTypes.Role, "User"));
    5  claims.Add(new Claim(ClaimTypes.Role, "Admin"));
    6  claims.Add(new Claim("PassPort", "E90000082"));
    7  string key = "fasdfad&9045dafz222#fadpio@0232";
    8  DateTime expires = DateTime.Now.AddDays(1);
    9  byte[] secBytes = Encoding.UTF8.GetBytes(key);
    10 var secKey = new SymmetricSecurityKey(secBytes);
    11 var credentials = new SigningCredentials(secKey,SecurityAlgorithms.HmacSha256Signature);
    12 var tokenDescriptor = new JwtSecurityToken(claims: claims,
    13     expires: expires, signingCredentials: credentials);
    14 string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    15 Console.WriteLine(jwt);
    
  3. 调用JwtSecurityTokenHandler类对JWT进行解码,因为它会在对JWT解码前对签名进行校验
    1  string jwt = Console.ReadLine()!;
    2  string secKey = "fasdfad&9045dafz222#fadpio@0232";
    3  JwtSecurityTokenHandler tokenHandler = new();
    4  TokenValidationParameters valParam = new ();
    5  var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secKey));
    6  valParam.IssuerSigningKey = securityKey;
    7  valParam.ValidateIssuer = false;
    8  valParam.ValidateAudience = false;
    9  ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwt,
    10         valParam,out SecurityToken secToken);
    11 foreach (var claim in claimsPrincipal.Claims)
    12 {
    13     Console.WriteLine($"{claim.Type}={claim.Value}");
    14 }
    

注意:

  • 一定不要把不能被客户端知道的信息放到负载中。
  • 当我们使用这个被篡改的JWT去运行代码8-11,程序运行时就会抛出内容为“Signature validation failed”的异常。

NET Core对于JWT的封装

  1. 当我们使用这个被篡改的JWT去运行代码8-11,程序运行时就会抛出内容为“Signature validation failed”的异常。
  2. NuGet为项目安装Microsoft.AspNetCore.Authentication.JwtBearer包
  3. 编写代码对JWT进行配置
    1  services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
    2  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    3  .AddJwtBearer(x =>
    4  {
    5     var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
    6     byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
    7     var secKey = new SymmetricSecurityKey(keyBytes);
    8     x.TokenValidationParameters = new()
    9     {
    10       ValidateIssuer=false, ValidateAudience=false, ValidateLifetime=true,
    11       ValidateIssuerSigningKey=true, IssuerSigningKey=secKey
    12    };
    13 });
    
  4. app.UseAuthorization之前添加app.UseAuthentication
  5. 类似上面生产JWT
  6. 在需要登录才能访问的控制器类上添加[Authorize]这个ASP.NET Core内置的Attribute

注:

  • 在前端项目中,我们可以把令牌保存到Cookie、LocalStorage等位置,从而在后续请求中重复使用
  • 而对于移动App、PC客户端,我们可以把令牌保存到配置文件中或者本地文件数据库中。

让Swagger中调试带验证的请求更简单

修改AddSwaggerGen()

1  builder.Services.AddSwaggerGen(c =>
2  {
3      var scheme = new OpenApiSecurityScheme()
4      {
5          Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'",
6          Reference = new OpenApiReference{Type = ReferenceType.SecurityScheme,
7              Id = "Authorization"},
8          Scheme = "oauth2",Name = "Authorization",
9          In = ParameterLocation.Header,Type = SecuritySchemeType.ApiKey,
10     };
11     c.AddSecurityDefinition("Authorization", scheme);
12     var requirement = new OpenApiSecurityRequirement();
13     requirement[scheme] = new List<string>();
14     c.AddSecurityRequirement(requirement);
15 });

解决JWT无法提前撤回的难题

JWT的缺点是:一旦JWT被发放给客户端,在有效期内这个令牌就一直有效,令牌是无法被提前撤回的。

解决思路是:在用户表中增加一个整数类型的列JWTVersion,它代表最后一次发放出去的令牌的版本号;每次登录、发放令牌的时候,我们都让JWTVersion的值自增,同时将JWTVersion的值也放到JWT的负载中;当执行禁用用户、撤回用户的令牌等操作的时候,我们让这个用户对应的JWTVersion的值自增;当服务器端收到客户端提交的JWT后,先把JWT中的JWTVersion值和数据库中的JWTVersion值做比较,如果JWT中JWTVersion的值小于数据库中JWTVersion的值,就说明这个JWT过期了,这样我们就实现了JWT的撤回机制。

JWT和Session比较

请添加图片描述

托管服务

使用:

  1. 继承BackgroudService

托管服务中使用依赖注入的陷阱

  • 托管服务是以单例的生命周期注册到依赖注入容器中的。

  • 长生命周期的服务不能依赖短生命周期的服务,因此我们可以在托管服务中通过构造方法注入其他生命周期为单例的服务,但是不能注入生命周期为范围或者瞬态的服务。

  • 我们可以通过构造方法注入IServiceScopeFactory服务,它可以用来创建IServiceScope对象,这样我们就可以通过IServiceScope来创建短生命周期的服务了

    this.serviceScope = scopeFactory.CreateScope();
    var sp = serviceScope.ServiceProvider;
    this.ctx = sp.GetRequiredService<TestDbContext>();
    
  1. services.AddHostedService()
  2. 由于IServiceScope继承了IDisposable接口,因此我们需要在托管服务的Dispose方法中销毁serviceScope。

请求数据校验

FluentValidation的基本使用

  1. 在项目中安装NuGet包FluentValidation.AspNetCore。
  2. 在Program.cs中添加注册相关服务的代码
     1  builder.Services.AddFluentValidation(fv => {
     2      Assembly assembly = Assembly.GetExecutingAssembly();
     3      fv.RegisterValidatorsFromAssembly(assembly);
     4  });
    
  3. 编写一个模型类Request
  4. 编写一个继承自AbstractValidator的数据校验类
     1  public class Login2RequestValidator: AbstractValidator<Login2Request>
     2  {
     3     public Login2RequestValidator()
     4     {
     5        RuleFor(x=>x.Email).NotNull().EmailAddress()
     6           .Must(v=>v.EndsWith("@qq.com")||v.EndsWith("@163.com"))
     7           .WithMessage("只支持QQ和163邮箱");
     8        RuleFor(x => x.Password).NotNull().Length(3, 10)
     9           .WithMessage("密码长度必须介于3到10之间")
     10          .Equal(x => x.Password2).WithMessage("两次密码必须一致");
     11    }
     12 }
    

SignalR

ASP.NET Core SignalR(以下简称SignalR)是.NET Core平台中对WebSocket的封装,从而让开发人员可以更简单地进行WebSocket开发。

基本使用:

  1. ASP.NET Core SignalR(以下简称SignalR)是.NET Core平台中对WebSocket的封装,从而让开发人员可以更简单地进行WebSocket开发。
  2. 编写方法
  3. 编辑Program.cs,在builder.Build之前调用builder.Services.AddSignalR注册所有SignalR的服务,在app.MapControllers之前调用app.MapHub(“/Hubs/ChatRoomHub”)启用SignalR中间件,并且设置当客户端通过SignalR请求“/Hubs/ChatRoomHub”这个路径的时候,由ChatRoomHub进行处理。

从中心调用客户端方法

await connection.InvokeAsync("SendMessage", 
    userTextBox.Text, messageTextBox.Text);

从客户端调用中心方法

connection.On<string, string>("ReceiveMessage", (user, message) =>
{
    this.Dispatcher.Invoke(() =>
    {
       var newMessage = $"{user}: {message}";
       messagesList.Items.Add(newMessage);
    });
});

部署

我们在开发环境中运行的项目所加载的程序集是为了方便开发工具调试而生成的调试版程序集,运行效率并不高,因此我们不能直接把项目文件夹下bin/Debug中的程序集部署到生产环境的服务器上。我们应该创建网站的发布版,创建网站发布版的过程简称为“发布”。

两种部署模式:“框架独立”和“独立”

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ASP的最新版本ASP.NET是Microsoft用于建立动态的数据库驱动网站的技术。内容包括:ASP.NET Web表单的使用,高级ASP.NET页面的开发,ADO.NET的使用,ASP.NET应用程序的使用, ASP.NET应用程序的保护,ASP.NET Web服务的建立,.NET框架的利用,自定义的ASP.NET控件的建立,ASP.NET的应用程序示例等。<br/>本书内容丰富、图文并茂,适合于需要创建网站的专业程序员阅读。本书是关于使用ASP.NET建立网站的完整参考书,书中包含数百个代码示例,读者可以使用这些示例开始建立自己的网站<br/>前言<br/><br/>第一部分 使用ASP.NET Web表单<br/><br/>第1章 建立ASP.NET页面 <br/><br/>1.1 ASP.NET和.NET框架 <br/>1.1.1 .NET框架类库 <br/>1.1.2 理解名称空间 <br/>1.1.3 标准的ASP.NET名称空间 <br/>1.1.4 与.NET框架兼容的语言 <br/>1.2 ASP.NET控件简介 <br/>1.2.1 简单的ASP.NET页面 <br/>1.2.2 ASP.NET控件的优点 <br/>1.2.3 ASP.NET控件概述 <br/>1.3 向ASP.NET页面中添加应用逻辑 <br/>1.3.1 处理控件事件 <br/>1.3.2 处理页面事件 <br/>1.4 ASP.NET页面的结构 <br/>1.4.1 指令 <br/>1.4.2 代码声明块 <br/>1.4.3 ASP.NET控件 <br/>1.4.4 代码显示块 <br/>1.4.5 服务器端注释 <br/>1.4.6 服务器端包含指令 <br/>1.4.7 文本和HTML标记 <br/>1.5 小结 <br/><br/>第2章 用Web服务器控件建立表单 <br/><br/>2.1 建立智能表单 <br/>2.1.1 Label控件<br/>2.1.2 TextBox控件 <br/>2.1.3 Button控件 <br/>2.1.4 RadioButton和RadioButtonList控件 <br/>2.1.5 CheckBox和CheckBoxList控件 <br/>2.1.6 DropDownList控件 <br/>2.1.7 ListBox控件 <br/>2.2 控制页面导航 <br/>2.2.1 将表单提交到另一个页面 <br/>2.2.2 使用Redirect( )方法 <br/>2.2.3 使用HyperLink控件 <br/>2.3 在控件上进行格式化 <br/>2.3.1 基本Web控件属性 <br/>2.3.2 在Web控件上应用样式 <br/>2.4 小结 <br/><br/>第3章 用检验控件执行表单的检验 <br/><br/>3.1 使用客户端检验 <br/>3.1.1 配置客户端检验 <br/>3.1.2 启用和禁用客户端检验 <br/>3.2 必填域:RequiredFieldValidator控件 <br/>3.3 检验表达式:RegularExpressionValidator控件 <br/>3.3.1 检验电子邮件地址 <br/>3.3.2 检验用户名和口令 <br/>3.3.3 检验电话号码 <br/>3.3.4 检验网址 <br/>3.3.5 检验条目长度 <br/>3.3.6 检验邮政编码 <br/>3.4 比较值:CompareValidator控件 <br/>3.4.1 比较一个控件与另一个控件的值 <br/>3.4.2 比较一个控件的值与固定值 <br/>3.4.3 执行数据类型检查 <br/>3.5 检查值的范围:RangeValidator控件 <br/>3.6 错误汇总:ValidationSummary控件 <br/>3.7 执行自定义的检验:CustomValidator控件 <br/>3.8 禁用检验 <br/>3.9 小结 <br/><br/>第4章 高级控件编程 <br/><br/>4.1 使用视图状态 <br/>4.1.1 禁用视图状态 <br/>4.1.2 将值添加到视图状态 <br/>4.2 显示和隐藏内容 <br/>4.2.1 使用Visible和Enabled属性 <br/>4.2.2 使用Panel控件 <br/>4.2.3 模拟多页面表单 <br/>4.2.4 通过程序添加控件 <br/>4.2.5 将控件添加到页面 <br/>4.2.6 PlaceHolder控件 <br/>4.2.7 动态产生表单 <br/>4.2.8 动态产生列表条目 <br/>4.3 使用复杂控件 <br/>4.3.1 使用Calendar控件显示交互式的日历 <br/>4.3.2 使用AdRotator控件显示广告条 <br/>4.3.3 使用HTMLInputFile控件接受文件上传 <br/>4.4 小结 <br/><br/>第二部分 高级ASP.NET页面开发<br/><br/>第5章 用用户控件创建自定义控件 <br/><br/>5.1 用用户控件包含标准内容 <br/>5.2 显露用户控件中的属性和方法 <br/>5.3 显露用户控件中的Web控件 <br/>5.4 显露用户控件中的事件 <br/>5.5 通过程序装载用户控件 <br/>5.6 小结 <br/><br/>第6章 将代码与表示分离 <br/><br/>6.1 创建业务组件 <br/>6.2 创建多层Web应用程序 <br/>6.3 使用code-behind <br/>6.3.1 code-behind是如何工作的 <br/>6.3.2 编译code-behind文件 <br/>6.3.3 从一个code-behind文件派生多个页面 <br/>6.3.4 编译完整的ASP.NET页面 <br/>6.4 小结 <br/><br/>第7章 用可移动控件为可移动设备服务 <br/><br/>7.1 使用可移动设备软件模拟器 <br/>7.2 无线应用协议简介 <br/>7.3 建立WML页面 <br/>7.3.1 配置IIS <br/>7.3.2 WML和XML <br/>7.3.3 创建卡片集 <br/>7.3.4 用WML链接文件 <br/>7.4 使用ASP.NET可移动控件 <br/>7.4.1 创建可移动表单 <br/>7.4.2 动态激活可移动表单 <br/>7.4.3 显示文本 <br/>7.4.4 显示列表 <br/>7.4.5 创建文本框 <br/>7.4.6 检验用户输入 <br/>7.4.7 显示图像 <br/>7.4.8 进行电话呼叫 <br/>7.4.9 用可移动控件显示广告 <br/>7.4.10 使用可移动控件显示日历 <br/>7.5 创建跨设备兼容的可移动页面 <br/>7.5.1 探测设备功能 <br/>7.5.2 使用DeviceSpecific选择设备 <br/>7.5.3 使用表单模板集 <br/>7.6 小结 <br/><br/>第8章 使用第三方控件 <br/><br/>8.1 使用Microsoft Internet Explorer WebControls <br/>8.1.1 使用TreeView控件 <br/>8.1.2 使用Toolbar控件 <br/>8.1.3 使用TabStrip控件 <br/>8.2 使用Superexpert控件 <br/>8.2.1 使用Content Rotator控件 <br/>8.2.2 使用SuperDataGrid控件 <br/>8.2.3 使用DataForm控件 <br/>8.3 小结 <br/><br/>第三部分 使用ADO.NET<br/><br/>第9章 ADO.NET简介 <br/><br/>9.1 ADO.NET概述 <br/>9.2 执行常见的数据库任务 <br/>9.2.1 打开数据库链接 <br/>9.2.2 从数据库表获取记录 <br/>9.2.3 在查询中使用参数 <br/>9.2.4 向数据库添加记录 <br/>9.2.5 更新数据库记录 <br/>9.2.6 删除数据库记录 <br/>9.3 改进数据库性能 <br/>9.3.1 使用SQL存储过程 <br/>9.3.2 获取返回值和输出参数 <br/>9.3.3 执行复杂的存储过程 <br/>9.3.4 用链接缓冲改进性能 <br/>9.4 高级的数据库主题 <br/>9.4.1 在事务中执行数据库命令 <br/>9.4.2 指定命令行为 <br/>9.4.3 获取表模式信息 <br/>9.5 小结 <br/><br/>第10章 将数据绑定到Web控件 <br/><br/>10.1 数据绑定概述 <br/>10.2 将服务器控件绑定到数据源 <br/>10.2.1 绑定到Repeater控件 <br/>10.2.2 使用模板 <br/>10.2.3 视图状态和Repeater控件 <br/>10.2.4 绑定到DropDownList控件 <br/>10.2.5 绑定到RadioButtonList控件 <br/>10.2.6 绑定到CheckBoxList控件 <br/>10.2.7 绑定到ListBox控件 <br/>10.2.8 绑定到其他控件 <br/>10.3 创建主/明细表单 <br/>10.3.1 创建单页面的主/明细表单 <br/>10.3.2 创建多页面的主/明细表单 <br/>10.4 小结 <br/><br/>第11章 使用DataList和DataGrid控件 <br/><br/>11.1 DataList和DataGrid控件概述 <br/>11.1.1 理解事件冒泡机制 <br/>11.1.2 使用模板 <br/>11.1.3 使用DataKeys集合 <br/>11.2 使用DataList控件 <br/>11.2.1 在DataList中显示数据 <br/>11.2.2 对DataList使用模板 <br/>11.2.3 对DataList使用样式 <br/>11.2.4 在DataList中创建多个列 <br/>11.2.5 捕获DataList控件中引发的事件 <br/>11.2.6 选择DataList中的条目 <br/>11.2.7 通过DataList控件使用DataKeys集合 <br/>11.2.8 用DataList控件创建主/明细表单 <br/>11.2.9 编辑DataList控件中的条目 <br/>11.3 使用DataGrid控件 <br/>11.3.1 在DataGrid控件中创建列 <br/>11.3.2 对DataGrid使用样式 <br/>11.3.3 对DataGrid控件中的列进行排序 <br/>11.3.4 对DataGrid中的记录进行分页 <br/>11.3.5 选择DataGrid控件中的行 <br/>11.3.6 编辑DataGrid控件中的条目 <br/>11.3.7 使用模板编辑DataGrid控件中的条目 <br/>11.4 小结 <br/><br/>第12章 操作DataSet <br/><br/>12.1 理解DataSet <br/>12.1.1 DataSet的元素 <br/>12.1.2 向DataSet添加DataTable <br/>12.1.3 将控件绑定到DataSet <br/>12.2 理解DataTable <br/>12.2.1 通过程序创建DataTable <br/>12.2.2 设置DataTable属性 <br/>12.2.3 在DataTable中设置列属性 <br/>12.2.4 在DataTable中计算列值 <br/>12.2.5 在DataTable之间定义关系 <br/>12.2.6 获取DataTable模式信息 <br/>12.2.7 在DataTable中更新记录 <br/>12.3 理解DataView <br/>12.3.1 在DataView中对行进行过滤 <br/>12.3.2 在DataView中对行进行排序 <br/>12.3.3 在DataView中搜索行 <br/>12.4 通过ASP.NET页面使用DataSet <br/>12.4.1 缓存DataSet <br/>12.4.2 显示缓存的数据 <br/>12.4.3 过滤缓存的数据 <br/>12.4.4 在缓存的DataSet中搜索行 <br/>12.5 小结 <br/><br/>第13章 操作XML <br/><br/>13.1 XML类概述 <br/>13.2 通过DataSet使用XML <br/>13.2.1 将XML文档读入DataSet <br/>13.2.2 通过ReadXml使用模式 <br/>13.2.3 从DataSet写XML文档 <br/>13.2.4 通过DataSet使用XMLData Documents <br/>13.3 使用样式单转换XML <br/>13.4 使用ASP.NET Xml控件 <br/>13.5 使用XslTransform类 <br/>13.6 使用强类型的DataSet <br/>13.7 小结 <br/><br/>第14章 使用ADO.NET创建搜索页面 <br/><br/>14.1 使用SQL Server全文搜索 <br/>14.1.1 配置Full-Text Search Service <br/>14.1.2 对数据库数据执行任意文本查询 <br/>14.1.3 对数据库数据执行布尔查询 <br/>14.1.4 上传二进制文档并且进行索引 <br/>14.2 使用Microsoft Indexing Service <br/>14.2.1 配置Microsoft Indexing Service <br/>14.2.2 将SQL Server配置为使用Microsoft Indexing Service <br/>14.2.3 获取文档属性 <br/>14.2.4 对文件系统数据执行任意文本查询 <br/>14.2.5 对文件系统数据执行布尔查询 <br/>14.2.6 对文档属性执行查询 <br/>14.3 小结 <br/><br/>第四部分 使用ASP.NET应用程序<br/><br/>第15章 创建ASP.NET应用程序 <br/><br/>15.1 ASP.NET应用程序概述 <br/>15.2 使用应用程序状态 <br/>15.2.1 理解应用程序状态和同步 <br/>15.2.2 使用Global.asax文件 <br/>15.2.3 理解Context和使用Global.asax文件 <br/>15.2.4 处理应用程序Start和Init事件 <br/>15.2.5 处理Application_BeginRequest事件 <br/>15.3 使用Web.Config文件 <br/>15.3.1 研究配置部分 <br/>15.3.2 修改配置设置 <br/>15.3.3 设置配置位置 <br/>15.3.4 锁定配置设置 <br/>15.3.5 添加自定义的配置信息 <br/>15.4 使用HTTP处理器和模块 <br/>15.4.1 使用HTTP处理器 <br/>15.4.2 使用HTTP模块 <br/>15.4.3 创建WhosOn应用程序 <br/>15.5 小结 <br/><br/>第16章 跟踪用户会话 <br/><br/>16.1 使用浏览器cookie <br/>16.1.1 cookie如何工作 <br/>16.1.2 创建和读取会话cookie <br/>16.1.3 创建和读取持久性cookie <br/>16.1.4 设置cookie属性 <br/>16.1.5 cookie的限制 <br/>16.2 使用会话状态 <br/>16.2.1 向会话状态中添加条目 <br/>16.2.2 从会话状态中删除条目 <br/>16.2.3 启动用户会话 <br/>16.2.4 结束用户会话 <br/>16.2.5 处理会话事件 <br/>16.2.6 在进程内存储会话状态 <br/>16.2.7 在Windows服务中存储会话状态 <br/>16.2.8 在数据库表中存储会话状态 <br/>16.2.9 禁止会话状态 <br/>16.3 使用无cookie会话 <br/>16.3.1 启用无cookie会话 <br/>16.3.2 无cookie会话的限制 <br/>16.4 小结 <br/><br/>第17章 缓存ASP.NET应用程序 <br/><br/>17.1 使用页面输出缓存 <br/>17.1.1 按参数改变缓存内容 <br/>17.1.2 按头改变缓存内容 <br/>17.1.3 按自定义的字符串改变缓存内容 <br/>17.1.4 设置缓存位置 <br/>17.1.5 使用HttpCachePolicy类 <br/>17.2 使用页面分段缓存 <br/>17.2.1 按参数改变页面分段缓存 <br/>17.2.2 页面分段缓存的限制 <br/>17.3 使用页面数据缓存 <br/>17.3.1 在缓存中添加条目 <br/>17.3.2 添加缓存文件依赖性 <br/>17.3.3 添加缓存触发器依赖性 <br/>17.3.4 添加缓存键依赖性 <br/>17.3.5 建立绝对的过期策略 <br/>17.3.6 建立变化的过期策略 <br/>17.3.7 设置缓存条目优先级 <br/>17.3.8 创建缓存回调方法 <br/>17.4 小结 <br/><br/>第18章 应用程序跟踪和错误处理 <br/><br/>18.1 响应错误 <br/>18.1.1 查看错误信息 <br/>18.1.2 页面级错误处理 <br/>18.1.3 应用程序级错误处理 <br/>18.2 跟踪和监视应用程序 <br/>18.2.1 跟踪页面的执行 <br/>18.2.2 监视ASP.NET进程 <br/>18.2.3 获取进程信息 <br/>18.3 记录事件 <br/>18.4 使用调试器 <br/>18.4.1 连接调试器 <br/>18.4.2 建立断点 <br/>18.4.3 建立观察 <br/>18.4.4 逐步执行ASP.NET页面 <br/>18.5 小结 <br/><br/>第五部分 保护ASP.NET应用程序<br/><br/>第19章 使用基于表单的身份验证 <br/><br/>19.1 使用表单身份验证 <br/>19.1.1 启用表单身份验证 <br/>19.1.2 配置表单身份验证 <br/>19.1.3 配置表单授权 <br/>19.1.4 获取用户信息 <br/>19.1.5 创建注销页面 <br/>19.1.6 用Web.Config文件进行用户身份验证 <br/>19.1.7 用XML文件进行用户身份验证 <br/>19.1.8 用数据库表进行用户身份验证 <br/>19.1.9 实现基于角色的身份验证 <br/>19.1.10 创建自定义的身份验证票据 <br/>19.1.11 表单身份验证和Web阵 <br/>19.2 使用Passport身份验证 <br/>19.2.1 启用Passport身份验证 <br/>19.2.2 允许用户登录和注销 <br/>19.2.3 获取用户信息 <br/>19.3 小结 <br/><br/>第20章 基于Windows的身份验证 <br/><br/>20.1 使用基于Windows的身份验证 <br/>20.1.1 配置Internet Information Server安全 <br/>20.1.2 配置Microsoft Windows安全 <br/>20.1.3 配置Windows身份验证 <br/>20.1.4 配置Windows授权 <br/>20.1.5 配置自定义角色 <br/>20.1.6 获取用户信息 <br/>20.2 模拟用户账号 <br/>20.3 设置安全策略 <br/>20.4 小结 <br/><br/>第21章 加密网上发送的数据 <br/><br/>21.1 使用Secure Sockets Layer <br/>21.1.1 加密 <br/>21.1.2 身份验证 <br/>21.1.3 数据完整性 <br/>21.1.4 SSL有多安全 <br/>21.2 将服务器配置为使用SSL <br/>21.2.1 产生证书请求文件 <br/>21.2.2 申请服务器证书 <br/>21.2.3 安装服务器证书 <br/>21.3 在ASP.NET页面中使用SSL <br/>21.4 使用.NET加密类 <br/>21.4.1 使用散列算法 <br/>21.4.2 使用对称加密算法 <br/>21.4.3 使用不对称加密 <br/>21.5 小结 <br/><br/>第六部分 建立ASP.NET Web服务<br/><br/>第22章 创建XML Web服务 <br/><br/>22.1 XML Web服务概述 <br/>22.1.1 XML Web服务促进通信 <br/>22.1.2 XML Web服务允许聚集 <br/>22.2 创建简单的XML Web服务 <br/>22.2.1 设置WebMethod属性 <br/>22.2.2 设置WebService属性 <br/>22.2.3 预编译XML Web服务 <br/>22.3 从浏览器测试XML Web服务 <br/>22.3.1 用HTTP-Get调用XML Web服务 <br/>22.3.2 用HTTP-Post调用XML Web服务 <br/>22.3.3 用SOAP调用XML Web服务 <br/>22.4 通过代理类访问XML Web服务 <br/>22.4.1 产生XML Web服务代理类 <br/>22.4.2 使用XML Web服务代理类 <br/>22.4.3 使用Web服务描述语言工具 <br/>22.4.4 设置代理类属性 <br/>22.5 在XML Web服务中传送复杂数据 <br/>22.5.1 XML Web服务和数组 <br/>22.5.2 XML Web服务和类 <br/>22.5.3 Web服务和DataSet <br/>22.5.4 Web服务和二进制文件 <br/>22.6 XML Web服务和网站的交互 <br/>22.6.1 XML Web服务和应用程序状态 <br/>22.6.2 XML Web服务和会话状态 <br/>22.7 小结<br/><br/>第23章 高级的XML Web服务 <br/><br/>23.1 使用WebService行为 <br/>23.1.1 WebService行为的限制 <br/>23.1.2 用WebService行为创建简单的页面 <br/>23.1.3 使用WebService行为回调函数 <br/>23.1.4 缓存WebService行为中的错误 <br/>23.1.5 使用WebService行为执行部分更新 <br/>23.1.6 使用WebService行为获取数据库数据 <br/>23.2 保护XML Web服务 <br/>23.2.1 安全的XML Web服务 <br/>23.2.2 创建数据库表 <br/>23.2.3 创建Login( )方法 <br/>23.2.4 获取自定义的SOAP头 <br/>23.2.5 验证会话键 <br/>23.2.6 缓存会话键 <br/>23.2.7 建立安全的XML Web服务 <br/>23.2.8 访问安全的Web服务 <br/>23.3 使用HTML模式匹配 <br/>23.3.1 创建WSDL文档 <br/>23.3.2 指定正则表达式模式 <br/>23.3.3 创建简单的HTML模式匹配服务 <br/>23.3.4 在HTML模式匹配中使用输入参数 <br/>23.3.5 建立一个Six Degrees Web服务 <br/>23.4 小结 <br/><br/>第七部分 利用.NET框架<br/><br/>第24章 操作集合和字符串 <br/><br/>24.1 使用集合 <br/>24.1.1 使用ArrayList集合 <br/>24.1.2 使用HashTable集合 <br/>24.1.3 使用SortedList集合 <br/>24.2 使用字符串 <br/>24.2.1 对字符串进行格式化 <br/>24.2.2 使用String方法和属性 <br/>24.2.3 使用StringBuilder类 <br/>24.3 使用正则表达式 <br/>24.3.1 使用正则表达式类 <br/>24.3.2 研究正则表达式的元素 <br/>24.3.3 匹配单一字符 <br/>24.3.4 匹配特殊字符 <br/>24.3.5 匹配字符的替代序列 <br/>24.3.6 使用数量词进行匹配 <br/>24.3.7 正则表达式的超范围问题 <br/>24.3.8 捕获和反向引用 <br/>24.3.9 使用替换模式 <br/>24.3.10 设置正则表达式选项 <br/>24.4 小结 <br/><br/>第25章 操作文件系统 <br/><br/>25.1 使用文件和目录 <br/>25.1.1 创建和读取文本文件 <br/>25.1.2 创建和读取二进制文件 <br/>25.1.3 显示目录的内容 <br/>25.1.4 获取路径信息 <br/>25.2 使用串行化 <br/>25.2.1 使用二进制串行化 <br/>25.2.2 使用XML串行化 <br/>25.3 小结 <br/><br/>第26章 发送电子邮件和访问网络 <br/><br/>26.1 从ASP.NET页面发送电子邮件 <br/>26.1.1 Microsoft SMTP Service <br/>26.1.2 发送简单的电子邮件 <br/>26.1.3 使用MailMessage类 <br/>26.1.4 向电子邮件消息添加附件 <br/>26.1.5 发送HTML电子邮件 <br/>26.2 使用Message Queuing <br/>26.2.1 配置Microsoft Message Queuing <br/>26.2.2 使用消息队列 <br/>26.2.3 向队列发送消息 <br/>26.2.4 从队列获取消息 <br/>26.2.5 显示消息体 <br/>26.2.6 发送和接收复杂的消息 <br/>26.3 使用HTTP协议访问其他网站 <br/>26.3.1 使用WebClient类 <br/>26.3.2 使用HttpWebRequest类 <br/>26.3.3 解析域名 <br/>26.4 小结 <br/><br/>第27章 用GDI+动态创建图形 <br/><br/>27.1 建立简单的图像 <br/>27.2 使用GDI+绘制对象 <br/>27.2.1 创建位图 <br/>27.2.2 使用Graphics对象 <br/>27.2.3 设置图形质量 <br/>27.2.4 使用颜色 <br/>27.2.5 使用画刷 <br/>27.2.6 使用画笔 <br/>27.2.7 绘制矩形 <br/>27.2.8 绘制直线 <br/>27.2.9 绘制椭圆 <br/>27.2.10 绘制曲线 <br/>27.3 绘制文本字符串 <br/>27.4 建立GDI+应用程序 <br/>27.4.1 创建ASP.NET绘图应用程序 <br/>27.4.2 创建简单的图表应用程序 <br/>27.5 小结 <br/><br/>第八部分 建立自定义的ASP.NET控件<br/><br/>第28章 开发自定义的控件 <br/><br/>28.1 创建简单的控件 <br/>28.2 使用HtmlTextWriter <br/>28.3 向控件添加属性和方法 <br/>28.3.1 使用属性访问函数 <br/>28.3.2 使用控件方法 <br/>28.4 解析内部内容 <br/>28.5 向控件添加子控件 <br/>28.6 自定义控件和事件 <br/>28.7 参与postback <br/>28.8 创建组合控件 <br/>28.9 派生现有的控件 <br/>28.10 访问当前上下文 <br/>28.11 调试控件 <br/>28.12 小结 <br/><br/>第29章 高级控件开发 <br/><br/>29.1 向控件添加模板 <br/>29.1.1 创建一个模板的多个实例 <br/>29.1.2 向一个控件添加多个模板 <br/>29.2 创建DataBound控件 <br/>29.2.1 实现不同的数据源 <br/>29.2.2 实现简单的DataBound控件 <br/>29.2.3 使用DataBound控件和模板 <br/>29.2.4 使用DataBound控件和状态 <br/>29.2.5 将自定义控件绑定到DataSet <br/>29.3 使用自定义控件和Web服务 <br/>29.3.1 创建特色产品Web服务 <br/>29.3.2 创建特色产品自定义控件 <br/>29.3.3 显示特色产品控件 <br/>29.4 小结 <br/><br/>第九部分 ASP.NET应用程序示例<br/><br/>第30章 创建职业介绍网站 <br/><br/>30.1 安装职业介绍网站 <br/>30.2 创建主页 <br/>30.3 对用户进行身份验证 <br/>30.4 创建虚拟URL <br/>30.5 列出和更新职位 <br/>30.6 创建职业介绍XML Web服务 <br/>30.7 小结 <br/><br/>第31章 创建网上商店 <br/><br/>31.1 ASP.NET Unleashed示例商店概述 <br/>31.2 安装ASP.NET Unleashed示例商店 <br/>31.3 建立导航系统 <br/>31.4 缓存产品数据 <br/>31.5 建立购物车 <br/>31.6 动态装载产品模板 <br/>31.6.1 使用LoadControl方法 <br/>31.6.2 code-behind产品模板 <br/>31.7 小结 <br/><br/>第十部分 附录<br/><br/>附录A 从ASP迁移到ASP.NET <br/>附录B HTML控件参考 <br/>附录C Web控件参考
第一部分 使用ASP.NET Web表单<br><br>第1章 建立ASP.NET页面 <br><br>1.1 ASP.NET和.NET框架 <br>1.1.1 .NET框架类库 <br>1.1.2 理解名称空间 <br>1.1.3 标准的ASP.NET名称空间 <br>1.1.4 与.NET框架兼容的语言 <br>1.2 ASP.NET控件简介 <br>1.2.1 简单的ASP.NET页面 <br>1.2.2 ASP.NET控件的优点 <br>1.2.3 ASP.NET控件概述 <br>1.3 向ASP.NET页面中添加应用逻辑 <br>1.3.1 处理控件事件 <br>1.3.2 处理页面事件 <br>1.4 ASP.NET页面的结构 <br>1.4.1 指令 <br>1.4.2 代码声明块 <br>1.4.3 ASP.NET控件 <br>1.4.4 代码显示块 <br>1.4.5 服务器端注释 <br>1.4.6 服务器端包含指令 <br>1.4.7 文本和HTML标记 <br>1.5 小结 <br><br>第2章 用Web服务器控件建立表单 <br><br>2.1 建立智能表单 <br>2.1.1 Label控件<br>2.1.2 TextBox控件 <br>2.1.3 Button控件 <br>2.1.4 RadioButton和RadioButtonList控件 <br>2.1.5 CheckBox和CheckBoxList控件 <br>2.1.6 DropDownList控件 <br>2.1.7 ListBox控件 <br>2.2 控制页面导航 <br>2.2.1 将表单提交到另一个页面 <br>2.2.2 使用Redirect( )方法 <br>2.2.3 使用HyperLink控件 <br>2.3 在控件上进行格式化 <br>2.3.1 基本Web控件属性 <br>2.3.2 在Web控件上应用样式 <br>2.4 小结 <br><br>第3章 用检验控件执行表单的检验 <br><br>3.1 使用客户端检验 <br>3.1.1 配置客户端检验 <br>3.1.2 启用和禁用客户端检验 <br>3.2 必填域:RequiredFieldValidator控件 <br>3.3 检验表达式:RegularExpressionValidator控件 <br>3.3.1 检验电子邮件地址 <br>3.3.2 检验用户名和口令 <br>3.3.3 检验电话号码 <br>3.3.4 检验网址 <br>3.3.5 检验条目长度 <br>3.3.6 检验邮政编码 <br>3.4 比较值:CompareValidator控件 <br>3.4.1 比较一个控件与另一个控件的值 <br>3.4.2 比较一个控件的值与固定值 <br>3.4.3 执行数据类型检查 <br>3.5 检查值的范围:RangeValidator控件 <br>3.6 错误汇总:ValidationSummary控件 <br>3.7 执行自定义的检验:CustomValidator控件 <br>3.8 禁用检验 <br>3.9 小结 <br><br>第4章 高级控件编程 <br><br>4.1 使用视图状态 <br>4.1.1 禁用视图状态 <br>4.1.2 将值添加到视图状态 <br>4.2 显示和隐藏内容 <br>4.2.1 使用Visible和Enabled属性 <br>4.2.2 使用Panel控件 <br>4.2.3 模拟多页面表单 <br>4.2.4 通过程序添加控件 <br>4.2.5 将控件添加到页面 <br>4.2.6 PlaceHolder控件 <br>4.2.7 动态产生表单 <br>4.2.8 动态产生列表条目 <br>4.3 使用复杂控件 <br>4.3.1 使用Calendar控件显示交互式的日历 <br>4.3.2 使用AdRotator控件显示广告条 <br>4.3.3 使用HTMLInputFile控件接受文件上传 <br>4.4 小结 <br><br>第二部分 高级ASP.NET页面开发<br><br>第5章 用用户控件创建自定义控件 <br><br>5.1 用用户控件包含标准内容 <br>5.2 显露用户控件中的属性和方法 <br>5.3 显露用户控件中的Web控件 <br>5.4 显露用户控件中的事件 <br>5.5 通过程序装载用户控件 <br>5.6 小结 <br><br>第6章 将代码与表示分离 <br><br>6.1 创建业务组件 <br>6.2 创建多层Web应用程序 <br>6.3 使用code-behind <br>6.3.1 code-behind是如何工作的 <br>6.3.2 编译code-behind文件 <br>6.3.3 从一个code-behind文件派生多个页面 <br>6.3.4 编译完整的ASP.NET页面 <br>6.4 小结 <br><br>第7章 用可移动控件为可移动设备服务 <br><br>7.1 使用可移动设备软件模拟器 <br>7.2 无线应用协议简介 <br>7.3 建立WML页面 <br>7.3.1 配置IIS <br>7.3.2 WML和XML <br>7.3.3 创建卡片集 <br>7.3.4 用WML链接文件 <br>7.4 使用ASP.NET可移动控件 <br>7.4.1 创建可移动表单 <br>7.4.2 动态激活可移动表单 <br>7.4.3 显示文本 <br>7.4.4 显示列表 <br>7.4.5 创建文本框 <br>7.4.6 检验用户输入 <br>7.4.7 显示图像 <br>7.4.8 进行电话呼叫 <br>7.4.9 用可移动控件显示广告 <br>7.4.10 使用可移动控件显示日历 <br>7.5 创建跨设备兼容的可移动页面 <br>7.5.1 探测设备功能 <br>7.5.2 使用DeviceSpecific选择设备 <br>7.5.3 使用表单模板集 <br>7.6 小结 <br><br>第8章 使用第三方控件 <br><br>8.1 使用Microsoft Internet Explorer WebControls <br>8.1.1 使用TreeView控件 <br>8.1.2 使用Toolbar控件 <br>8.1.3 使用TabStrip控件 <br>8.2 使用Superexpert控件 <br>8.2.1 使用Content Rotator控件 <br>8.2.2 使用SuperDataGrid控件 <br>8.2.3 使用DataForm控件 <br>8.3 小结 <br><br>第三部分 使用ADO.NET<br><br>第9章 ADO.NET简介 <br><br>9.1 ADO.NET概述 <br>9.2 执行常见的数据库任务 <br>9.2.1 打开数据库链接 <br>9.2.2 从数据库表获取记录 <br>9.2.3 在查询中使用参数 <br>9.2.4 向数据库添加记录 <br>9.2.5 更新数据库记录 <br>9.2.6 删除数据库记录 <br>9.3 改进数据库性能 <br>9.3.1 使用SQL存储过程 <br>9.3.2 获取返回值和输出参数 <br>9.3.3 执行复杂的存储过程 <br>9.3.4 用链接缓冲改进性能 <br>9.4 高级的数据库主题 <br>9.4.1 在事务中执行数据库命令 <br>9.4.2 指定命令行为 <br>9.4.3 获取表模式信息 <br>9.5 小结 <br><br>第10章 将数据绑定到Web控件 <br><br>10.1 数据绑定概述 <br>10.2 将服务器控件绑定到数据源 <br>10.2.1 绑定到Repeater控件 <br>10.2.2 使用模板 <br>10.2.3 视图状态和Repeater控件 <br>10.2.4 绑定到DropDownList控件 <br>10.2.5 绑定到RadioButtonList控件 <br>10.2.6 绑定到CheckBoxList控件 <br>10.2.7 绑定到ListBox控件 <br>10.2.8 绑定到其他控件 <br>10.3 创建主/明细表单 <br>10.3.1 创建单页面的主/明细表单 <br>10.3.2 创建多页面的主/明细表单 <br>10.4 小结 <br><br>第11章 使用DataList和DataGrid控件 <br><br>11.1 DataList和DataGrid控件概述 <br>11.1.1 理解事件冒泡机制 <br>11.1.2 使用模板 <br>11.1.3 使用DataKeys集合 <br>11.2 使用DataList控件 <br>11.2.1 在DataList中显示数据 <br>11.2.2 对DataList使用模板 <br>11.2.3 对DataList使用样式 <br>11.2.4 在DataList中创建多个列 <br>11.2.5 捕获DataList控件中引发的事件 <br>11.2.6 选择DataList中的条目 <br>11.2.7 通过DataList控件使用DataKeys集合 <br>11.2.8 用DataList控件创建主/明细表单 <br>11.2.9 编辑DataList控件中的条目 <br>11.3 使用DataGrid控件 <br>11.3.1 在DataGrid控件中创建列 <br>11.3.2 对DataGrid使用样式 <br>11.3.3 对DataGrid控件中的列进行排序 <br>11.3.4 对DataGrid中的记录进行分页 <br>11.3.5 选择DataGrid控件中的行 <br>11.3.6 编辑DataGrid控件中的条目 <br>11.3.7 使用模板编辑DataGrid控件中的条目 <br>11.4 小结 <br><br>第12章 操作DataSet <br><br>12.1 理解DataSet <br>12.1.1 DataSet的元素 <br>12.1.2 向DataSet添加DataTable <br>12.1.3 将控件绑定到DataSet <br>12.2 理解DataTable <br>12.2.1 通过程序创建DataTable <br>12.2.2 设置DataTable属性 <br>12.2.3 在DataTable中设置列属性 <br>12.2.4 在DataTable中计算列值 <br>12.2.5 在DataTable之间定义关系 <br>12.2.6 获取DataTable模式信息 <br>12.2.7 在DataTable中更新记录 <br>12.3 理解DataView <br>12.3.1 在DataView中对行进行过滤 <br>12.3.2 在DataView中对行进行排序 <br>12.3.3 在DataView中搜索行 <br>12.4 通过ASP.NET页面使用DataSet <br>12.4.1 缓存DataSet <br>12.4.2 显示缓存的数据 <br>12.4.3 过滤缓存的数据 <br>12.4.4 在缓存的DataSet中搜索行 <br>12.5 小结 <br><br>第13章 操作XML <br><br>13.1 XML类概述 <br>13.2 通过DataSet使用XML <br>13.2.1 将XML文档读入DataSet <br>13.2.2 通过ReadXml使用模式 <br>13.2.3 从DataSet写XML文档 <br>13.2.4 通过DataSet使用XMLData Documents <br>13.3 使用样式单转换XML <br>13.4 使用ASP.NET Xml控件 <br>13.5 使用XslTransform类 <br>13.6 使用强类型的DataSet <br>13.7 小结 <br><br>第14章 使用ADO.NET创建搜索页面 <br><br>14.1 使用SQL Server全文搜索 <br>14.1.1 配置Full-Text Search Service <br>14.1.2 对数据库数据执行任意文本查询 <br>14.1.3 对数据库数据执行布尔查询 <br>14.1.4 上传二进制文档并且进行索引 <br>14.2 使用Microsoft Indexing Service <br>14.2.1 配置Microsoft Indexing Service <br>14.2.2 将SQL Server配置为使用Microsoft Indexing Service <br>14.2.3 获取文档属性 <br>14.2.4 对文件系统数据执行任意文本查询 <br>14.2.5 对文件系统数据执行布尔查询 <br>14.2.6 对文档属性执行查询 <br>14.3 小结 <br><br>第四部分 使用ASP.NET应用程序<br><br>第15章 创建ASP.NET应用程序 <br><br>15.1 ASP.NET应用程序概述 <br>15.2 使用应用程序状态 <br>15.2.1 理解应用程序状态和同步 <br>15.2.2 使用Global.asax文件 <br>15.2.3 理解Context和使用Global.asax文件 <br>15.2.4 处理应用程序Start和Init事件 <br>15.2.5 处理Application_BeginRequest事件 <br>15.3 使用Web.Config文件 <br>15.3.1 研究配置部分 <br>15.3.2 修改配置设置 <br>15.3.3 设置配置位置 <br>15.3.4 锁定配置设置 <br>15.3.5 添加自定义的配置信息 <br>15.4 使用HTTP处理器和模块 <br>15.4.1 使用HTTP处理器 <br>15.4.2 使用HTTP模块 <br>15.4.3 创建WhosOn应用程序 <br>15.5 小结 <br><br>第16章 跟踪用户会话 <br><br>16.1 使用浏览器cookie <br>16.1.1 cookie如何工作 <br>16.1.2 创建和读取会话cookie <br>16.1.3 创建和读取持久性cookie <br>16.1.4 设置cookie属性 <br>16.1.5 cookie的限制 <br>16.2 使用会话状态 <br>16.2.1 向会话状态中添加条目 <br>16.2.2 从会话状态中删除条目 <br>16.2.3 启动用户会话 <br>16.2.4 结束用户会话 <br>16.2.5 处理会话事件 <br>16.2.6 在进程内存储会话状态 <br>16.2.7 在Windows服务中存储会话状态 <br>16.2.8 在数据库表中存储会话状态 <br>16.2.9 禁止会话状态 <br>16.3 使用无cookie会话 <br>16.3.1 启用无cookie会话 <br>16.3.2 无cookie会话的限制 <br>16.4 小结 <br><br>第17章 缓存ASP.NET应用程序 <br><br>17.1 使用页面输出缓存 <br>17.1.1 按参数改变缓存内容 <br>17.1.2 按头改变缓存内容 <br>17.1.3 按自定义的字符串改变缓存内容 <br>17.1.4 设置缓存位置 <br>17.1.5 使用HttpCachePolicy类 <br>17.2 使用页面分段缓存 <br>17.2.1 按参数改变页面分段缓存 <br>17.2.2 页面分段缓存的限制 <br>17.3 使用页面数据缓存 <br>17.3.1 在缓存中添加条目 <br>17.3.2 添加缓存文件依赖性 <br>17.3.3 添加缓存触发器依赖性 <br>17.3.4 添加缓存键依赖性 <br>17.3.5 建立绝对的过期策略 <br>17.3.6 建立变化的过期策略 <br>17.3.7 设置缓存条目优先级 <br>17.3.8 创建缓存回调方法 <br>17.4 小结 <br><br>第18章 应用程序跟踪和错误处理 <br><br>18.1 响应错误 <br>18.1.1 查看错误信息 <br>18.1.2 页面级错误处理 <br>18.1.3 应用程序级错误处理 <br>18.2 跟踪和监视应用程序 <br>18.2.1 跟踪页面的执行 <br>18.2.2 监视ASP.NET进程 <br>18.2.3 获取进程信息 <br>18.3 记录事件 <br>18.4 使用调试器 <br>18.4.1 连接调试器 <br>18.4.2 建立断点 <br>18.4.3 建立观察 <br>18.4.4 逐步执行ASP.NET页面 <br>18.5 小结 <br><br>第五部分 保护ASP.NET应用程序<br><br>第19章 使用基于表单的身份验证 <br><br>19.1 使用表单身份验证 <br>19.1.1 启用表单身份验证 <br>19.1.2 配置表单身份验证 <br>19.1.3 配置表单授权 <br>19.1.4 获取用户信息 <br>19.1.5 创建注销页面 <br>19.1.6 用Web.Config文件进行用户身份验证 <br>19.1.7 用XML文件进行用户身份验证 <br>19.1.8 用数据库表进行用户身份验证 <br>19.1.9 实现基于角色的身份验证 <br>19.1.10 创建自定义的身份验证票据 <br>19.1.11 表单身份验证和Web阵 <br>19.2 使用Passport身份验证 <br>19.2.1 启用Passport身份验证 <br>19.2.2 允许用户登录和注销 <br>19.2.3 获取用户信息 <br>19.3 小结 <br><br>第20章 基于Windows的身份验证 <br><br>20.1 使用基于Windows的身份验证 <br>20.1.1 配置Internet Information Server安全 <br>20.1.2 配置Microsoft Windows安全 <br>20.1.3 配置Windows身份验证 <br>20.1.4 配置Windows授权 <br>20.1.5 配置自定义角色 <br>20.1.6 获取用户信息 <br>20.2 模拟用户账号 <br>20.3 设置安全策略 <br>20.4 小结 <br><br>第21章 加密网上发送的数据 <br><br>21.1 使用Secure Sockets Layer <br>21.1.1 加密 <br>21.1.2 身份验证 <br>21.1.3 数据完整性 <br>21.1.4 SSL有多安全 <br>21.2 将服务器配置为使用SSL <br>21.2.1 产生证书请求文件 <br>21.2.2 申请服务器证书 <br>21.2.3 安装服务器证书 <br>21.3 在ASP.NET页面中使用SSL <br>21.4 使用.NET加密类 <br>21.4.1 使用散列算法 <br>21.4.2 使用对称加密算法 <br>21.4.3 使用不对称加密 <br>21.5 小结 <br><br>第六部分 建立ASP.NET Web服务<br><br>第22章 创建XML Web服务 <br><br>22.1 XML Web服务概述 <br>22.1.1 XML Web服务促进通信 <br>22.1.2 XML Web服务允许聚集 <br>22.2 创建简单的XML Web服务 <br>22.2.1 设置WebMethod属性 <br>22.2.2 设置WebService属性 <br>22.2.3 预编译XML Web服务 <br>22.3 从浏览器测试XML Web服务 <br>22.3.1 用HTTP-Get调用XML Web服务 <br>22.3.2 用HTTP-Post调用XML Web服务 <br>22.3.3 用SOAP调用XML Web服务 <br>22.4 通过代理类访问XML Web服务 <br>22.4.1 产生XML Web服务代理类 <br>22.4.2 使用XML Web服务代理类 <br>22.4.3 使用Web服务描述语言工具 <br>22.4.4 设置代理类属性 <br>22.5 在XML Web服务中传送复杂数据 <br>22.5.1 XML Web服务和数组 <br>22.5.2 XML Web服务和类 <br>22.5.3 Web服务和DataSet <br>22.5.4 Web服务和二进制文件 <br>22.6 XML Web服务和网站的交互 <br>22.6.1 XML Web服务和应用程序状态 <br>22.6.2 XML Web服务和会话状态 <br>22.7 小结<br><br>第23章 高级的XML Web服务 <br><br>23.1 使用WebService行为 <br>23.1.1 WebService行为的限制 <br>23.1.2 用WebService行为创建简单的页面 <br>23.1.3 使用WebService行为回调函数 <br>23.1.4 缓存WebService行为中的错误 <br>23.1.5 使用WebService行为执行部分更新 <br>23.1.6 使用WebService行为获取数据库数据 <br>23.2 保护XML Web服务 <br>23.2.1 安全的XML Web服务 <br>23.2.2 创建数据库表 <br>23.2.3 创建Login( )方法 <br>23.2.4 获取自定义的SOAP头 <br>23.2.5 验证会话键 <br>23.2.6 缓存会话键 <br>23.2.7 建立安全的XML Web服务 <br>23.2.8 访问安全的Web服务 <br>23.3 使用HTML模式匹配 <br>23.3.1 创建WSDL文档 <br>23.3.2 指定正则表达式模式 <br>23.3.3 创建简单的HTML模式匹配服务 <br>23.3.4 在HTML模式匹配中使用输入参数 <br>23.3.5 建立一个Six Degrees Web服务 <br>23.4 小结 <br><br>第七部分 利用.NET框架<br><br>第24章 操作集合和字符串 <br><br>24.1 使用集合 <br>24.1.1 使用ArrayList集合 <br>24.1.2 使用HashTable集合 <br>24.1.3 使用SortedList集合 <br>24.2 使用字符串 <br>24.2.1 对字符串进行格式化 <br>24.2.2 使用String方法和属性 <br>24.2.3 使用StringBuilder类 <br>24.3 使用正则表达式 <br>24.3.1 使用正则表达式类 <br>24.3.2 研究正则表达式的元素 <br>24.3.3 匹配单一字符 <br>24.3.4 匹配特殊字符 <br>24.3.5 匹配字符的替代序列 <br>24.3.6 使用数量词进行匹配 <br>24.3.7 正则表达式的超范围问题 <br>24.3.8 捕获和反向引用 <br>24.3.9 使用替换模式 <br>24.3.10 设置正则表达式选项 <br>24.4 小结 <br><br>第25章 操作文件系统 <br><br>25.1 使用文件和目录 <br>25.1.1 创建和读取文本文件 <br>25.1.2 创建和读取二进制文件 <br>25.1.3 显示目录的内容 <br>25.1.4 获取路径信息 <br>25.2 使用串行化 <br>25.2.1 使用二进制串行化 <br>25.2.2 使用XML串行化 <br>25.3 小结 <br><br>第26章 发送电子邮件和访问网络 <br><br>26.1 从ASP.NET页面发送电子邮件 <br>26.1.1 Microsoft SMTP Service <br>26.1.2 发送简单的电子邮件 <br>26.1.3 使用MailMessage类 <br>26.1.4 向电子邮件消息添加附件 <br>26.1.5 发送HTML电子邮件 <br>26.2 使用Message Queuing <br>26.2.1 配置Microsoft Message Queuing <br>26.2.2 使用消息队列 <br>26.2.3 向队列发送消息 <br>26.2.4 从队列获取消息 <br>26.2.5 显示消息体 <br>26.2.6 发送和接收复杂的消息 <br>26.3 使用HTTP协议访问其他网站 <br>26.3.1 使用WebClient类 <br>26.3.2 使用HttpWebRequest类 <br>26.3.3 解析域名 <br>26.4 小结 <br><br>第27章 用GDI+动态创建图形 <br><br>27.1 建立简单的图像 <br>27.2 使用GDI+绘制对象 <br>27.2.1 创建位图 <br>27.2.2 使用Graphics对象 <br>27.2.3 设置图形质量 <br>27.2.4 使用颜色 <br>27.2.5 使用画刷 <br>27.2.6 使用画笔 <br>27.2.7 绘制矩形 <br>27.2.8 绘制直线 <br>27.2.9 绘制椭圆 <br>27.2.10 绘制曲线 <br>27.3 绘制文本字符串 <br>27.4 建立GDI+应用程序 <br>27.4.1 创建ASP.NET绘图应用程序 <br>27.4.2 创建简单的图表应用程序 <br>27.5 小结 <br><br>第八部分 建立自定义的ASP.NET控件<br><br>第28章 开发自定义的控件 <br><br>28.1 创建简单的控件 <br>28.2 使用HtmlTextWriter <br>28.3 向控件添加属性和方法 <br>28.3.1 使用属性访问函数 <br>28.3.2 使用控件方法 <br>28.4 解析内部内容 <br>28.5 向控件添加子控件 <br>28.6 自定义控件和事件 <br>28.7 参与postback <br>28.8 创建组合控件 <br>28.9 派生现有的控件 <br>28.10 访问当前上下文 <br>28.11 调试控件 <br>28.12 小结 <br><br>第29章 高级控件开发 <br><br>29.1 向控件添加模板 <br>29.1.1 创建一个模板的多个实例 <br>29.1.2 向一个控件添加多个模板 <br>29.2 创建DataBound控件 <br>29.2.1 实现不同的数据源 <br>29.2.2 实现简单的DataBound控件 <br>29.2.3 使用DataBound控件和模板 <br>29.2.4 使用DataBound控件和状态 <br>29.2.5 将自定义控件绑定到DataSet <br>29.3 使用自定义控件和Web服务 <br>29.3.1 创建特色产品Web服务 <br>29.3.2 创建特色产品自定义控件 <br>29.3.3 显示特色产品控件 <br>29.4 小结 <br><br>第九部分 ASP.NET应用程序示例<br><br>第30章 创建职业介绍网站 <br><br>30.1 安装职业介绍网站 <br>30.2 创建主页 <br>30.3 对用户进行身份验证 <br>30.4 创建虚拟URL <br>30.5 列出和更新职位 <br>30.6 创建职业介绍XML Web服务 <br>30.7 小结 <br><br>第31章 创建网上商店 <br><br>31.1 ASP.NET Unleashed示例商店概述 <br>31.2 安装ASP.NET Unleashed示例商店 <br>31.3 建立导航系统 <br>31.4 缓存产品数据 <br>31.5 建立购物车 <br>31.6 动态装载产品模板 <br>31.6.1 使用LoadControl方法 <br>31.6.2 code-behind产品模板 <br>31.7 小结 <br><br>第十部分 附录<br><br>附录A 从ASP迁移到ASP.NET <br>附录B HTML控件参考 <br>附录C Web控件参考<br>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值