.NET程序员面试全攻略:从基础到实战技巧

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《.NET程序员面试秘笈》是一本专为.NET开发者量身打造的面试指南,内容涵盖.NET框架核心知识、C#高级特性、数据库操作、Web开发、多线程编程及软件工程实践等多个方面。本书结合理论与实战,帮助读者系统复习技术要点,掌握常见面试问题应对策略,提升面试成功率,适用于.NET Framework与.NET Core开发者。
NET程序员面试秘笈

1. .NET框架基础知识

.NET框架是由微软开发的一种软件开发平台,支持多种编程语言,其中C#是最常用的语言之一。它为应用程序开发提供了全面的类库支持和强大的运行时环境。

.NET框架的体系结构与核心组件

.NET框架主要包括公共语言运行时(CLR)、.NET类库、ASP.NET、Windows窗体(WinForms)、Windows Presentation Foundation(WPF)等核心组件。CLR是整个框架的核心,负责代码的执行、内存管理、垃圾回收等任务。

// 示例:一个简单的.NET控制台程序
using System;

namespace HelloDotNet
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, .NET Framework!");
        }
    }
}

上述代码展示了在.NET框架下编写的一个最基础的控制台应用程序。 Main 方法是程序的入口点, Console.WriteLine 用于向控制台输出字符串。该程序在CLR环境中运行,体现了.NET框架对托管代码的执行支持。

2. C#面向对象编程(封装、继承、多态)

面向对象编程(Object-Oriented Programming,简称OOP)是C#语言的核心编程范式之一。它通过类和对象来组织代码,使程序结构更清晰、逻辑更严谨、维护更方便。本章将深入探讨C#中的三大核心特性: 封装 继承 多态 。我们将从基础概念入手,逐步深入到访问控制、构造函数、接口设计、虚方法等进阶内容,并通过代码示例与逻辑分析,帮助读者构建完整的OOP知识体系。

2.1 面向对象编程基础

在C#中,面向对象编程的基础是 类(class)与对象(object) 。类是对象的模板,对象是类的具体实例。理解类与对象的关系,是掌握OOP的第一步。

2.1.1 类与对象的基本定义

在C#中,类使用 class 关键字定义,对象通过 new 运算符创建。以下是一个简单的类定义示例:

public class Person
{
    public string Name;
    public int Age;

    public void SayHello()
    {
        Console.WriteLine($"Hello, my name is {Name}, I am {Age} years old.");
    }
}
逻辑分析:
  • Person 是一个类名,表示一个人的基本信息和行为。
  • Name Age 是成员变量(字段),表示对象的属性。
  • SayHello() 是成员方法,表示对象的行为。
  • public 表示访问权限,允许外部访问。

创建对象并调用方法的示例:

Person p = new Person();
p.Name = "Alice";
p.Age = 30;
p.SayHello();  // 输出:Hello, my name is Alice, I am 30 years old.
参数说明:
  • new Person() :创建一个 Person 类的实例。
  • p.Name = "Alice" :设置对象的 Name 属性。
  • p.SayHello() :调用对象的方法。

2.1.2 成员变量与成员方法的访问控制

C#提供了多种访问修饰符来控制类成员的可访问性,包括:

修饰符 可访问范围
public 所有代码可访问
private 仅类内部可访问
protected 类及其派生类可访问
internal 同一程序集内可访问
protected internal 同一程序集或派生类中可访问
示例代码:
public class BankAccount
{
    private decimal balance;  // 只能在类内部访问

    public void Deposit(decimal amount)
    {
        if (amount > 0)
            balance += amount;
    }

    public decimal GetBalance()
    {
        return balance;
    }
}
逻辑分析:
  • private decimal balance :限制外部直接访问余额字段,避免非法操作。
  • Deposit() 方法提供了安全的存入逻辑。
  • GetBalance() 方法用于获取余额,遵循封装原则。

2.1.3 构造函数与析构函数的作用

构造函数用于在创建对象时初始化对象的状态,而析构函数用于在对象被销毁时释放资源。

示例代码:
public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }

    // 构造函数
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    // 析构函数(不常手动定义)
    ~Product()
    {
        Console.WriteLine($"{Name} is being disposed.");
    }
}
创建对象并释放资源:
Product p = new Product("Laptop", 999.99m);
Console.WriteLine($"Product: {p.Name}, Price: {p.Price}");
p = null;
GC.Collect();  // 强制垃圾回收,触发析构函数
参数说明:
  • Product(string name, decimal price) :构造函数接受参数用于初始化对象。
  • ~Product() :析构函数在对象被回收前执行,用于资源清理(如文件流、数据库连接等)。
  • GC.Collect() :强制执行垃圾回收机制,通常由CLR自动管理。

2.2 封装与继承机制

封装和继承是面向对象编程的两大核心机制。封装用于隐藏实现细节,继承用于实现代码复用。

2.2.1 属性封装与get/set访问器

封装通过属性(property)实现,属性结合 get set 访问器,可以控制字段的读写权限。

示例代码:
public class Student
{
    private string name;

    public string Name
    {
        get { return name; }
        set
        {
            if (!string.IsNullOrEmpty(value))
                name = value;
            else
                throw new ArgumentException("Name cannot be empty.");
        }
    }
}
使用示例:
Student s = new Student();
s.Name = "Bob";  // 调用 set
Console.WriteLine(s.Name);  // 调用 get
逻辑分析:
  • private string name :隐藏字段,防止外部直接修改。
  • Name 属性提供受控访问,确保数据合法性。

2.2.2 基类与派生类的关系

继承允许一个类从另一个类继承字段和方法。被继承的类称为基类(base class),继承的类称为派生类(derived class)。

示例代码:
// 基类
public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Animal sound");
    }
}

// 派生类
public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Woof!");
    }
}
使用示例:
Animal a = new Dog();
a.MakeSound();  // 输出:Woof!
逻辑分析:
  • Dog 继承自 Animal ,获得其所有公共成员。
  • override 关键字用于重写基类的虚方法,实现多态。

2.2.3 protected和internal访问修饰符的应用

protected internal 用于控制类成员在继承和程序集内的可见性。

示例代码:
public class BaseClass
{
    protected int protectedField;
    internal int internalField;
}

public class DerivedClass : BaseClass
{
    public void AccessFields()
    {
        protectedField = 10;  // 可访问
        internalField = 20;   // 可访问
    }
}
访问权限说明:
  • protectedField :只能在 BaseClass 及其派生类中访问。
  • internalField :同一程序集内可访问。

2.3 多态与接口设计

多态(Polymorphism)是指同一接口可以有多种实现方式。它通过虚方法、接口和抽象类实现。

2.3.1 虚方法与重写方法的实现

虚方法使用 virtual 定义,派生类通过 override 重写其实现。

示例代码:
public class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("Drawing a shape.");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a circle.");
    }
}

public class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a rectangle.");
    }
}
多态调用:
List<Shape> shapes = new List<Shape>
{
    new Circle(),
    new Rectangle()
};

foreach (var shape in shapes)
{
    shape.Draw();
}
输出结果:
Drawing a circle.
Drawing a rectangle.
逻辑分析:
  • virtual void Draw() :定义可被重写的虚方法。
  • override void Draw() :派生类提供自己的实现。
  • 多态允许统一接口调用不同实现。

2.3.2 接口的定义与实现

接口定义一组方法签名,类或结构体可以实现多个接口,从而支持多重继承。

示例代码:
public interface ILogger
{
    void Log(string message);
}

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }
}
使用示例:
ILogger logger = new ConsoleLogger();
logger.Log("This is a log message.");
逻辑分析:
  • ILogger 是一个接口,定义了 Log 方法。
  • ConsoleLogger 实现了该接口,提供具体的日志输出逻辑。

2.3.3 抽象类与接口的比较

抽象类和接口都用于定义行为规范,但它们有本质区别:

特性 抽象类 接口
成员实现 可以包含实现 只定义签名(C# 8.0后可有默认实现)
构造函数 支持 不支持
多重继承 不支持(只能继承一个类) 支持(实现多个接口)
成员访问权限 支持 public protected 默认 public
应用场景 共享实现代码 定义契约,实现多态
示例对比:
// 抽象类
public abstract class Vehicle
{
    public abstract void StartEngine();
    public void StopEngine()
    {
        Console.WriteLine("Engine stopped.");
    }
}

// 接口
public interface IVehicle
{
    void StartEngine();
}
实现类:
public class Car : Vehicle, IVehicle
{
    public override void StartEngine()
    {
        Console.WriteLine("Car engine started.");
    }
}
逻辑分析:
  • Vehicle 提供部分实现(如 StopEngine() )。
  • IVehicle 纯契约定义,强调行为一致性。
  • Car 同时继承抽象类并实现接口,展示多重继承能力。

流程图说明:

下图展示了继承与多态的关系流程:

graph TD
    A[Shape] --> B(Circle)
    A --> C[Rectangle]
    B --> D[Draw()]
    C --> E[Draw()]
    F[Shape shape = new Circle()] --> G[shape.Draw() => Circle.Draw()]

表格说明:

下表对比了类、接口和抽象类的主要特性:

特性 接口 抽象类
是否可实例化
是否可有实现 ✅(默认实现)
是否可多重继承
是否可有构造函数
是否可定义字段
是否支持访问控制

本章通过层层递进的方式,从面向对象的基本概念入手,逐步深入到封装、继承与多态的核心机制,并通过代码示例、表格对比和流程图说明,帮助读者构建完整的OOP知识体系,为后续的高级编程打下坚实基础。

3. C#高级特性(Lambda、LINQ、async/await)

在现代C#开发中,Lambda表达式、LINQ(Language Integrated Query)和 async/await 异步编程模型是提升代码简洁性、可读性和性能的关键特性。它们不仅简化了数据处理与异步操作,还极大地增强了开发者对复杂业务逻辑的掌控能力。本章将从基础语法出发,深入解析这些特性的原理、使用场景及优化策略。

3.1 Lambda表达式与委托

3.1.1 委托的基本语法与使用

委托(Delegate)是C#中实现回调机制的基础,它类似于函数指针,但具备类型安全性。委托可以指向一个或多个方法,并通过委托实例来调用这些方法。

基本语法:

public delegate int Calculate(int a, int b);

使用示例:

public class Program
{
    public delegate int Calculate(int a, int b);

    public static int Add(int a, int b)
    {
        return a + b;
    }

    public static void Main()
    {
        Calculate calc = Add;
        int result = calc(5, 3);
        Console.WriteLine(result); // 输出8
    }
}

参数说明与逻辑分析:

  • Calculate 是一个委托类型,表示一个接受两个整数参数并返回整数的方法。
  • Add 方法符合委托签名,可以赋值给 Calculate 类型的变量。
  • 调用 calc(5, 3) 实际上是调用了 Add 方法。

3.1.2 Lambda表达式的写法与应用场景

Lambda表达式是一种简洁的匿名函数写法,广泛用于LINQ查询、事件处理和委托赋值。

基本语法:

(parameters) => expression

使用示例:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
foreach (var num in evenNumbers)
{
    Console.WriteLine(num); // 输出2,4
}

逻辑分析:

  • n => n % 2 == 0 是一个Lambda表达式,表示接收一个整数参数 n 并返回布尔值。
  • Where 方法接受一个 Func<int, bool> 委托,Lambda表达式作为参数传入,筛选出偶数。

3.1.3 Func与Action委托的使用方式

Func<T, TResult> Action<T> 是系统预定义的通用委托类型,分别用于返回值的方法和无返回值的方法。

委托类型 描述 示例
Func<T1, T2, ..., TResult> 接收多个参数,返回一个结果 Func<int, int, int>
Action<T1, T2, ...> 接收多个参数,无返回值 Action<string>

使用示例:

Func<int, int, int> multiply = (x, y) => x * y;
Console.WriteLine(multiply(4, 5)); // 输出20

Action<string> greet = name => Console.WriteLine($"Hello, {name}");
greet("Alice"); // 输出Hello, Alice

逻辑分析:

  • Func<int, int, int> 表示一个接收两个整数并返回整数的函数。
  • Action<string> 表示一个接收字符串参数但不返回值的函数。

3.2 LINQ查询语法与执行机制

3.2.1 查询表达式与方法语法的区别

LINQ 提供了两种查询语法:查询表达式(Query Syntax)和方法语法(Method Syntax)。

类型 特点 示例
查询表达式 类似SQL语法,适合复杂查询 from x in list where x > 5 select x
方法语法 使用扩展方法链式调用 list.Where(x => x > 5)

示例对比:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// 查询表达式
var querySyntax = from n in numbers
                  where n % 2 == 0
                  select n;

// 方法语法
var methodSyntax = numbers.Where(n => n % 2 == 0);

逻辑分析:

  • 查询表达式更易读,适用于复杂查询。
  • 方法语法更灵活,适用于链式操作和动态条件。

3.2.2 延迟执行与立即执行的区别

LINQ 查询默认是 延迟执行(Deferred Execution) ,即查询不会立即执行,而是在枚举时才执行。

延迟执行示例:

var query = numbers.Where(n => n > 3);
numbers.Add(6); // 查询尚未执行
foreach (var item in query)
{
    Console.WriteLine(item); // 输出4,5,6
}

立即执行示例:

var list = numbers.Where(n => n > 3).ToList();
numbers.Add(7); // 查询已执行
foreach (var item in list)
{
    Console.WriteLine(item); // 输出4,5,6
}

流程图说明:

graph TD
A[定义查询] --> B{是否立即执行?}
B -->|是| C[执行并缓存结果]
B -->|否| D[每次枚举时重新执行]

3.2.3 LINQ to Objects与LINQ to SQL的实践

LINQ 可用于多种数据源,其中 LINQ to Objects 针对内存集合, LINQ to SQL 针对数据库查询。

LINQ to Objects 示例:

List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
var filtered = names.Where(n => n.Length > 3).ToList();

LINQ to SQL 示例(EF Core):

using (var context = new AppDbContext())
{
    var users = context.Users
                       .Where(u => u.Age > 18)
                       .Select(u => u.Name)
                       .ToList();
}

对比分析表格:

特性 LINQ to Objects LINQ to SQL
数据源 内存集合 数据库表
执行方式 本地执行 转换为SQL远程执行
性能 适用于小数据 支持大数据量优化
延迟执行 是(需支持)

3.3 异步编程模型(async/await)

3.3.1 async/await关键字的基本用法

async/await 是C#中实现异步编程的核心机制,使异步代码看起来像同步代码,提升可读性和可维护性。

基本结构:

public async Task<int> DownloadDataAsync()
{
    using (var client = new HttpClient())
    {
        string result = await client.GetStringAsync("https://example.com");
        return result.Length;
    }
}

逻辑分析:

  • async 标记方法为异步方法。
  • await 等待异步操作完成,释放当前线程资源。
  • 返回值为 Task<int> ,表示异步操作的结果。

3.3.2 Task与Thread的区别

特性 Thread Task
抽象级别 低级线程控制 高级任务抽象
异步支持 需手动管理 内置支持async/await
返回值 无法直接获取 支持返回值和异常
线程池 需手动控制 自动使用线程池

代码对比:

// Thread
Thread thread = new Thread(() => Console.WriteLine("Thread Work"));
thread.Start();

// Task
Task task = Task.Run(() => Console.WriteLine("Task Work"));
await task;

3.3.3 异步操作中的异常处理与ConfigureAwait

异步方法中,异常不会立即抛出,而是通过 Task.Exception 属性捕获。

异常处理示例:

try
{
    await DownloadDataAsync();
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

ConfigureAwait说明:

  • ConfigureAwait(false) :不恢复原始上下文(如UI线程),适用于库方法。
  • ConfigureAwait(true) :默认行为,恢复原始上下文。
string result = await client.GetStringAsync(url).ConfigureAwait(false);

逻辑分析:

  • 使用 ConfigureAwait(false) 可避免死锁,提高性能,尤其在库代码中推荐使用。
  • UI线程或ASP.NET请求上下文需保留上下文时使用 true

本章通过深入分析Lambda表达式、LINQ查询机制和异步编程模型,帮助开发者掌握现代C#开发的核心技能。下一章将进入.NET类库的实战应用,探讨泛型集合、文件操作与多线程编程等内容。

4. .NET类库使用(System.Collections.Generic、System.IO、System.Threading)

.NET 提供了丰富的类库支持,开发者可以借助这些类库高效地完成数据操作、文件读写、并发处理等任务。本章将重点介绍 System.Collections.Generic 泛型集合类、 System.IO 文件与流操作类,以及 System.Threading 多线程与异步编程类库,帮助开发者深入理解这些常用类库的使用方式与性能优化策略。

4.1 泛型集合与数据结构

在 .NET 开发中,泛型集合是处理数据集合的核心工具。 System.Collections.Generic 命名空间提供了多种高效、类型安全的集合类,如 List<T> Dictionary<TKey, TValue> HashSet<T> SortedDictionary<TKey, TValue> 以及线程安全的 ConcurrentDictionary<TKey, TValue> 等。

4.1.1 List 与 Dictionary 的常用操作

List<T> 是一种动态数组,适合需要频繁添加和访问元素的场景。 Dictionary<TKey, TValue> 是基于哈希表实现的键值对集合,适用于需要通过键快速查找值的场景。

// 示例:List<T> 和 Dictionary<TKey, TValue> 的基本使用
List<string> names = new List<string>();
names.Add("Alice");
names.Add("Bob");
names.Remove("Alice");

Dictionary<int, string> idToName = new Dictionary<int, string>();
idToName.Add(1, "Alice");
idToName[2] = "Bob";

foreach (var name in names)
{
    Console.WriteLine(name);
}

foreach (var pair in idToName)
{
    Console.WriteLine($"ID: {pair.Key}, Name: {pair.Value}");
}

逐行分析:

  • List<string> names = new List<string>(); 创建一个字符串类型的列表。
  • names.Add() 添加元素。
  • names.Remove() 移除指定元素。
  • Dictionary<int, string> 创建键为整数、值为字符串的字典。
  • idToName.Add() 添加键值对。
  • idToName[2] = "Bob" 使用索引器直接赋值。
  • foreach 遍历集合,输出结果。

性能对比:

集合类型 插入效率 查找效率 删除效率 适用场景
List<T> O(1) O(n) O(n) 顺序访问、频繁插入
Dictionary<TKey, TValue> O(1) O(1) O(1) 键值对查找、快速检索

4.1.2 HashSet 与 SortedDictionary 的使用场景

HashSet<T> 是一个不包含重复元素的集合,适合用于去重、快速判断是否存在某个元素。 SortedDictionary<TKey, TValue> 是基于红黑树实现的有序字典,适合需要按键排序的场景。

// 示例:HashSet<T> 和 SortedDictionary<TKey, TValue> 的使用
HashSet<string> uniqueNames = new HashSet<string>();
uniqueNames.Add("Alice");
uniqueNames.Add("Bob");
uniqueNames.Add("Alice");  // 不会重复添加

SortedDictionary<int, string> sortedDict = new SortedDictionary<int, string>();
sortedDict.Add(3, "Charlie");
sortedDict.Add(1, "Alice");
sortedDict.Add(2, "Bob");

Console.WriteLine("Unique names:");
foreach (var name in uniqueNames)
{
    Console.WriteLine(name);
}

Console.WriteLine("Sorted dictionary:");
foreach (var pair in sortedDict)
{
    Console.WriteLine($"ID: {pair.Key}, Name: {pair.Value}");
}

逐行分析:

  • HashSet<string> 自动去重,即使添加重复元素也不会报错。
  • SortedDictionary<int, string> 会自动按键排序,输出顺序为 1, 2, 3。
  • 使用 foreach 遍历输出结果。

适用场景对比:

集合类型 有序性 去重性 查找效率 适用场景
HashSet<T> O(1) 快速去重、集合操作
SortedDictionary<TKey, TValue> O(log n) 按键排序、范围查询

4.1.3 线程安全集合类(如 ConcurrentDictionary)

在多线程环境下,普通集合类可能引发线程安全问题。 System.Collections.Concurrent 命名空间提供了线程安全的集合类,如 ConcurrentDictionary<TKey, TValue> ,适用于并发访问的场景。

// 示例:ConcurrentDictionary<TKey, TValue> 的多线程使用
using System.Threading.Tasks;

ConcurrentDictionary<int, string> concurrentDict = new ConcurrentDictionary<int, string>();

Parallel.For(0, 100, i =>
{
    concurrentDict.TryAdd(i, $"Value_{i}");
});

foreach (var pair in concurrentDict)
{
    Console.WriteLine($"Key: {pair.Key}, Value: {pair.Value}");
}

逐行分析:

  • 使用 ConcurrentDictionary 替代普通字典,避免线程冲突。
  • TryAdd 方法在并发环境中安全添加键值对。
  • Parallel.For 模拟多线程并发操作。

线程安全集合类对比:

类型 是否线程安全 是否支持并发读写 特点
Dictionary<TKey, TValue> 简单高效,需自行加锁
ConcurrentDictionary<TKey, TValue> 支持高并发,适用于多线程环境
ConcurrentBag<T> 无序集合,适用于对象缓存等场景

4.2 文件与流操作

在 .NET 中, System.IO 命名空间提供了丰富的文件与流操作类,开发者可以使用 FileStream StreamReader StreamWriter 等类进行文件读写操作。此外, Path Directory 类提供了路径与目录的管理功能, BinaryFormatter XmlSerializer JsonSerializer 支持对象的序列化与反序列化。

4.2.1 FileStream 与 StreamReader/StreamWriter 的使用

graph TD
    A[打开文件] --> B[创建 FileStream]
    B --> C{读写模式}
    C -->|读取| D[使用 StreamReader]
    C -->|写入| E[使用 StreamWriter]
    D --> F[读取内容]
    E --> G[写入内容]
    F --> H[关闭流]
    G --> H
// 示例:FileStream + StreamReader 读取文件
using (FileStream fs = new FileStream("example.txt", FileMode.Open, FileAccess.Read))
using (StreamReader sr = new StreamReader(fs))
{
    string content = sr.ReadToEnd();
    Console.WriteLine(content);
}

// 示例:StreamWriter 写入文件
using (FileStream fs = new FileStream("output.txt", FileMode.Create, FileAccess.Write))
using (StreamWriter sw = new StreamWriter(fs))
{
    sw.WriteLine("Hello, .NET!");
}

逐行分析:

  • FileStream 用于打开或创建文件流。
  • StreamReader StreamWriter 分别用于读取和写入文本内容。
  • using 语句确保流在使用完毕后自动关闭。

4.2.2 文件路径与目录操作(Path 与 Directory 类)

Path 类提供了路径字符串操作方法,如获取文件名、扩展名、合并路径等。 Directory 类用于操作目录,如创建、删除、遍历目录等。

string path = @"C:\Projects\Example\file.txt";
Console.WriteLine("文件名:" + Path.GetFileName(path));        // 输出 file.txt
Console.WriteLine("扩展名:" + Path.GetExtension(path));       // 输出 .txt
Console.WriteLine("目录名:" + Path.GetDirectoryName(path));   // 输出 C:\Projects\Example

// 创建目录
Directory.CreateDirectory(@"C:\Projects\NewFolder");

// 获取目录下所有文件
string[] files = Directory.GetFiles(@"C:\Projects\Example");
foreach (var file in files)
{
    Console.WriteLine(file);
}

常用方法总结:

类名 方法名 用途
Path GetFileName 获取文件名
GetExtension 获取扩展名
Combine 拼接路径
Directory CreateDirectory 创建目录
GetFiles 获取目录下所有文件

4.2.3 序列化与反序列化(Binary、XML、JSON)

.NET 支持多种序列化方式,如二进制、XML 和 JSON。以下是一个使用 System.Text.Json 的 JSON 序列化示例:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Person person = new Person { Name = "Alice", Age = 30 };
string json = JsonSerializer.Serialize(person);
Console.WriteLine(json);  // 输出 {"Name":"Alice","Age":30}

Person deserialized = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine(deserialized.Name);  // 输出 Alice

参数说明:

  • JsonSerializer.Serialize() :将对象转换为 JSON 字符串。
  • JsonSerializer.Deserialize<T>() :将 JSON 字符串反序列化为指定类型的对象。

4.3 多线程与异步操作

在现代应用程序中,多线程和异步编程是提升性能与响应能力的关键手段。 System.Threading 命名空间提供了 Thread ThreadPool Task 等类用于并发编程。

4.3.1 Thread 类与线程生命周期

// 示例:使用 Thread 类创建线程
void PrintNumbers()
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine($"Thread: {i}");
        Thread.Sleep(500);
    }
}

Thread thread = new Thread(new ThreadStart(PrintNumbers));
thread.Start();
thread.Join();  // 主线程等待子线程完成
Console.WriteLine("Main thread completed.");

逐行分析:

  • ThreadStart 是线程执行的方法委托。
  • thread.Start() 启动线程。
  • thread.Join() 阻塞主线程,直到子线程执行完毕。

4.3.2 ThreadPool 与 Task 的使用对比

ThreadPool 是线程池,用于管理多个线程,适合执行短期任务。 Task 是基于 ThreadPool 的高级抽象,支持异步编程模型。

// 示例:使用 ThreadPool
ThreadPool.QueueUserWorkItem(state =>
{
    Console.WriteLine("ThreadPool task executed.");
});

// 示例:使用 Task
Task task = Task.Run(() =>
{
    Console.WriteLine("Task executed.");
});
task.Wait();  // 等待任务完成

性能对比:

特性 Thread ThreadPool Task
线程控制
异步支持
异常处理 需手动 需手动 自动
资源管理 手动 自动 自动

4.3.3 异步文件读写与网络请求

异步操作可以避免阻塞主线程,提高应用程序响应速度。以下是一个异步读取文件的示例:

async Task ReadFileAsync(string filePath)
{
    using (StreamReader reader = new StreamReader(filePath))
    {
        string content = await reader.ReadToEndAsync();
        Console.WriteLine(content);
    }
}

// 调用异步方法
await ReadFileAsync("example.txt");

逻辑分析:

  • async Task 表示这是一个异步方法。
  • await reader.ReadToEndAsync() 异步读取文件内容。
  • 使用 await 可以避免阻塞主线程,提升用户体验。

异步网络请求示例:

async Task<string> GetWebContentAsync(string url)
{
    using (HttpClient client = new HttpClient())
    {
        string result = await client.GetStringAsync(url);
        return result;
    }
}

string html = await GetWebContentAsync("https://example.com");
Console.WriteLine(html.Substring(0, 100));  // 输出前100个字符

总结:

  • 使用 async/await 可以简化异步代码逻辑。
  • 异步操作适用于 I/O 密集型任务,如文件读写、网络请求等。
  • 异步代码不会阻塞主线程,有助于提升应用程序的响应能力。

5. .NET Core架构与跨平台开发

.NET Core 是微软推出的跨平台、高性能、模块化的开发框架,其核心设计目标是实现代码的高可移植性、良好的性能表现以及灵活的部署能力。本章将深入剖析 .NET Core 的架构特点,探讨其在跨平台开发中的核心优势,同时围绕项目结构、依赖注入、环境配置、日志管理以及容器化部署等内容,展开系统性的讲解与实践。

跨平台开发的核心优势

为什么选择跨平台开发?

跨平台开发的核心在于“一次编写,到处运行”的理念。.NET Core 的设计初衷就是为了打破 Windows 系统的限制,支持 Linux、macOS 甚至嵌入式设备等多种平台。这种架构优势为开发者带来了以下几个方面的提升:

优势维度 说明
灵活性 可在多种操作系统上运行,适应不同部署环境
性能优化 更轻量化的运行时,启动速度快,资源占用低
社区活跃 开源项目推动生态快速发展,兼容性强
云原生支持 支持 Docker、Kubernetes 等现代云原生技术
企业级应用适配 可无缝集成微服务、API 网关等架构体系

.NET Core 的跨平台实现机制

.NET Core 通过统一的运行时(CoreCLR)和编译器(CoreRT)实现了跨平台的能力。其关键机制如下:

graph TD
    A[源代码 C#] --> B[编译为 IL 中间语言]
    B --> C[CoreCLR 运行时]
    C --> D1[Windows Runtime]
    C --> D2[Linux Runtime]
    C --> D3[macOS Runtime]
    D1 & D2 & D3 --> E[最终平台执行]

如上图所示,C# 源代码被编译为中间语言(IL),然后由 CoreCLR 根据不同平台的运行时进行解释和执行。这种设计保证了程序在不同操作系统上的行为一致性。

实际应用场景举例

以一个跨平台的 Web API 项目为例,开发者可以在 Windows 上使用 Visual Studio 编写代码,然后在 Linux 上部署运行,完全无需修改代码逻辑:

dotnet publish -c Release -r linux-x64

该命令会将项目发布为适用于 Linux 的自包含运行包,便于跨平台部署。

项目结构与.csproj文件的配置

标准项目结构解析

一个典型的 .NET Core 项目结构如下:

MyApp/
├── Program.cs
├── Startup.cs
├── appsettings.json
├── MyApp.csproj
└── Controllers/
    └── HomeController.cs
  • Program.cs :程序入口,定义 Main 方法,创建并运行 Web 主机。
  • Startup.cs :配置服务与中间件,定义请求管道。
  • appsettings.json :配置文件,存储环境相关参数。
  • Controllers/ :存放 MVC 或 Web API 控制器类。
  • MyApp.csproj :项目配置文件,定义依赖项、SDK 版本等信息。

.csproj 文件详解

.csproj 文件是 .NET Core 项目的核心配置文件,使用 XML 格式定义项目属性。以下是一个典型的 .csproj 文件内容:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <OutputType>Exe</OutputType>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="5.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0" />
  </ItemGroup>
</Project>

关键配置项说明:

配置项 说明
<TargetFramework> 指定目标框架版本,如 net5.0、net6.0 等
<OutputType> 定义输出类型, Exe 为可执行程序, Library 为类库
<RuntimeIdentifier> 指定运行时标识符,用于发布自包含应用
<PackageReference> 引用 NuGet 包及其版本

多目标框架配置(Multi-targeting)

开发者可以通过修改 .csproj 文件,让项目同时支持多个目标框架,例如:

<TargetFramework>net5.0;netcoreapp3.1</TargetFramework>

这样可以实现一套代码兼容多个 .NET Core 版本,适用于需要长期维护的项目。

依赖注入与中间件的使用

依赖注入(Dependency Injection)机制

.NET Core 内置了轻量级的依赖注入容器,支持构造函数注入、方法注入等多种方式。开发者可以在 Startup.cs ConfigureServices 方法中注册服务:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSingleton<IMyService, MyService>();
    services.AddScoped<IRepository, MyRepository>();
    services.AddTransient<IHelper, Helper>();
}

服务生命周期说明:

生命周期 说明
AddSingleton 整个应用程序生命周期中共享一个实例
AddScoped 每个请求范围内共享一个实例
AddTransient 每次请求都创建一个新实例

中间件(Middleware)管道构建

中间件是 ASP.NET Core 中处理请求和响应的核心机制。开发者可以在 Startup.cs Configure 方法中添加中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

每个中间件按照注册顺序依次执行,形成请求处理管道。例如:

graph LR
    A[请求] --> B[UseRouting]
    B --> C[UseAuthentication]
    C --> D[UseAuthorization]
    D --> E[UseEndpoints]
    E --> F[响应]

ASP.NET Core中的环境配置与日志管理

环境配置(Environment Configuration)

ASP.NET Core 支持通过 appsettings.json appsettings.{Environment}.json 文件进行环境配置。例如:

// appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  }
}

// appsettings.Development.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Debug"
    }
  }
}

通过 IConfiguration 接口可以读取配置值:

public class HomeController : ControllerBase
{
    private readonly IConfiguration _config;

    public HomeController(IConfiguration config)
    {
        _config = config;
    }

    [HttpGet]
    public string Get()
    {
        return _config["Logging:LogLevel:Default"];
    }
}

日志管理(Logging)

.NET Core 提供了内置的日志接口 ILogger<T> ,支持多种日志提供程序(如 Console、Debug、EventSource 等):

public class MyService
{
    private readonly ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public void DoSomething()
    {
        _logger.LogInformation("执行了 DoSomething 方法");
    }
}

日志级别说明:

级别 说明
Trace 最详细的日志信息
Debug 调试信息
Information 一般信息
Warning 警告信息
Error 错误信息
Critical 严重错误信息

跨平台部署与Docker容器化实践

跨平台部署方式

.NET Core 支持以下几种部署方式:

  1. 依赖框架部署(Framework-dependent) :运行时需安装 .NET Core SDK。
  2. 自包含部署(Self-contained) :将运行时与应用程序打包在一起,适用于没有 .NET Core 环境的机器。

例如,使用 CLI 发布自包含 Linux 应用:

dotnet publish -c Release -r linux-x64 --self-contained

Docker 容器化部署

Docker 是当前最流行的容器化部署方案。以下是使用 Docker 部署 .NET Core 应用的基本步骤:

1. 编写 Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /app

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "MyApp.dll"]
2. 构建并运行 Docker 镜像
docker build -t myapp .
docker run -d -p 8080:80 myapp
3. 查看容器日志
docker logs <container_id>

容器化部署的优势

优势 说明
一致性 开发、测试、生产环境一致
可移植性 可部署到任意支持 Docker 的主机
自动化部署 易于与 CI/CD 集成
资源隔离 容器之间互不影响,提高稳定性

本章系统地介绍了 .NET Core 的架构特点与跨平台开发实践,涵盖了项目结构、依赖注入、中间件、日志配置以及 Docker 容器化部署等多个核心内容。通过本章的学习,开发者应能够熟练构建、配置并部署跨平台的 .NET Core 应用程序,为后续的微服务架构与云原生开发打下坚实基础。

6. 数据库访问技术与ORM框架

数据库访问是现代应用程序开发中不可或缺的一部分。随着.NET平台的发展,开发者可以使用多种方式来访问和操作数据库,包括传统的ADO.NET和现代的ORM框架如Entity Framework Core(EF Core)。本章将深入讲解这些数据库访问技术的核心机制、使用方法以及性能优化策略,帮助开发者在实际项目中做出合理的技术选型。

6.1 ADO.NET数据库访问

作为.NET平台最早提供的数据库访问技术,ADO.NET提供了直接操作数据库的能力,适用于对性能要求高、需要精细控制SQL执行的场景。

6.1.1 连接字符串与数据库连接管理

连接字符串是建立数据库连接的基础。它通常包含服务器地址、数据库名称、身份验证方式等信息。

string connectionString = "Server=localhost;Database=TestDB;User Id=sa;Password=yourStrong(!)Password;";

参数说明:

  • Server :SQL Server实例的地址;
  • Database :要连接的数据库名称;
  • User Id Password :用于认证的用户名和密码;
  • 对于Windows身份验证,可使用 Integrated Security=true 替代用户名和密码。

连接管理技巧:

  • 使用 using 语句确保连接在使用后自动释放资源;
  • 合理使用连接池,避免频繁打开/关闭连接。

6.1.2 SqlCommand与SqlDataReader的使用

SqlCommand 类用于执行SQL语句, SqlDataReader 用于高效地读取查询结果。

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    string query = "SELECT Id, Name FROM Users";
    using (SqlCommand command = new SqlCommand(query, connection))
    {
        using (SqlDataReader reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                int id = reader.GetInt32(0);
                string name = reader.GetString(1);
                Console.WriteLine($"ID: {id}, Name: {name}");
            }
        }
    }
}

逻辑分析:

  • SqlConnection 建立连接;
  • SqlCommand 执行查询;
  • SqlDataReader 遍历查询结果;
  • reader.GetInt32(0) 表示获取第0列(即Id列)的整数值;
  • 使用嵌套的 using 保证资源释放。

6.1.3 参数化查询与事务处理

参数化查询能有效防止SQL注入,而事务处理则确保多个操作的原子性。

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    SqlTransaction transaction = connection.BeginTransaction();

    try
    {
        string insertQuery = "INSERT INTO Users (Name, Email) VALUES (@Name, @Email)";
        using (SqlCommand command = new SqlCommand(insertQuery, connection, transaction))
        {
            command.Parameters.AddWithValue("@Name", "Alice");
            command.Parameters.AddWithValue("@Email", "alice@example.com");
            command.ExecuteNonQuery();
        }

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        Console.WriteLine("Transaction rolled back. Error: " + ex.Message);
    }
}

逻辑分析:

  • 使用 BeginTransaction() 开启事务;
  • 多个SQL命令共享同一事务对象;
  • 如果出现异常,调用 Rollback() 回滚事务;
  • Parameters.AddWithValue 添加参数,防止SQL注入。

6.2 Entity Framework Core简介

Entity Framework Core 是微软推出的轻量级ORM框架,支持多种数据库平台,并提供LINQ查询、迁移、上下文管理等高级功能。

6.2.1 EF Core的安装与配置

在项目中使用EF Core,需通过NuGet安装以下包:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools

配置上下文类:

public class AppDbContext : DbContext
{
    public DbSet<User> Users { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=localhost;Database=TestDB;User Id=sa;Password=yourStrong(!)Password;");
    }
}

逻辑分析:

  • 继承 DbContext 构建上下文;
  • 使用 DbSet<T> 定义实体集;
  • OnConfiguring 方法中指定数据库连接。

6.2.2 数据模型的定义与迁移

EF Core支持通过代码生成数据库结构,使用迁移(Migration)机制同步模型变更。

dotnet ef migrations add InitialCreate
dotnet ef database update

数据模型示例:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

流程图展示迁移过程:

graph TD
    A[创建实体类] --> B[添加迁移]
    B --> C[生成SQL脚本]
    C --> D[更新数据库]

6.2.3 查询与更新操作的实现

EF Core支持LINQ查询语法,实现对数据库的灵活操作。

using (var context = new AppDbContext())
{
    // 查询
    var users = context.Users.Where(u => u.Name.StartsWith("A")).ToList();

    // 更新
    var user = context.Users.FirstOrDefault(u => u.Id == 1);
    if (user != null)
    {
        user.Email = "newemail@example.com";
        context.SaveChanges();
    }
}

逻辑分析:

  • Where() 生成对应的SQL查询语句;
  • ToList() 触发查询执行;
  • 修改实体后调用 SaveChanges() 提交更改;
  • EF Core自动跟踪实体状态并生成UPDATE语句。

6.3 ORM性能优化技巧

尽管ORM简化了数据库操作,但在高并发或大数据量场景下仍需优化以提升性能。

6.3.1 延迟加载与贪婪加载的对比

加载方式 特点 适用场景
延迟加载 仅在访问导航属性时加载关联数据,可能导致N+1查询问题 关联数据不总是需要时
贪婪加载 使用 Include() 一次性加载主表与关联表数据,减少数据库往返次数 需要频繁访问关联数据时

示例代码:

// 延迟加载
var user = context.Users.FirstOrDefault(u => u.Id == 1);
var orders = user.Orders;  // 触发第二次查询

// 贪婪加载
var userWithOrders = context.Users
    .Include(u => u.Orders)
    .FirstOrDefault(u => u.Id == 1);

6.3.2 显式加载与查询优化

显式加载适用于按需加载关联数据,避免不必要的查询开销。

var user = context.Users.FirstOrDefault(u => u.Id == 1);
context.Entry(user).Collection(u => u.Orders).Load();

逻辑分析:

  • 使用 Entry() 获取实体条目;
  • Collection().Load() 显式加载Orders集合;
  • 可以结合条件过滤加载特定子集。

6.3.3 EF Core中使用原始SQL的场景

在某些复杂查询或性能瓶颈场景下,可使用原始SQL语句绕过LINQ解析器。

var users = context.Users
    .FromSqlRaw("SELECT * FROM Users WHERE Name LIKE 'A%'")
    .ToList();

适用场景:

  • LINQ不支持的数据库函数或语法;
  • 性能敏感的查询;
  • 避免EF Core的查询转换开销。

本章系统地介绍了从基础的ADO.NET数据库访问到现代ORM框架EF Core的使用方法,并结合性能优化策略帮助开发者在不同场景下做出合适的技术选择。下一章将深入探讨ASP.NET MVC与Web API的开发实践,为构建Web服务提供理论与实践基础。

7. ASP.NET MVC与Web API开发实践

7.1 MVC模式的核心原理与组件

ASP.NET MVC 是一种基于 Model-View-Controller 模式的 Web 开发框架,它将应用程序逻辑清晰地分离为三个核心组件:

  • Model(模型) :处理数据和业务逻辑。通常与数据库交互,例如使用 Entity Framework。
  • View(视图) :负责用户界面的呈现,通常使用 Razor 引擎来渲染 HTML。
  • Controller(控制器) :接收用户请求,处理输入,并决定将哪个视图返回给用户。

MVC 模式的核心优势在于 职责分离 易于测试 ,使得前端与后端开发可以并行进行。

MVC 请求处理流程示意图:

graph TD
    A[客户端请求] --> B[路由系统]
    B --> C[控制器]
    C --> D[模型获取数据]
    D --> E[视图渲染]
    E --> F[响应返回客户端]

在 ASP.NET MVC 中,控制器继承自 Controller 基类,其方法称为 Action 方法 ,用于处理 HTTP 请求(GET、POST 等)。

示例:一个简单的控制器
public class HomeController : Controller
{
    public IActionResult Index()
    {
        ViewBag.Message = "欢迎使用ASP.NET MVC!";
        return View();
    }
}
  • IActionResult 是一个接口,表示 Action 方法返回的响应结果。
  • ViewBag 是一个动态对象,用于传递数据给视图。
  • return View() 表示返回视图页面 Index.cshtml

7.2 控制器与视图的交互机制

控制器与视图之间的通信主要通过以下几种方式进行:

  1. ViewBag / ViewData :用于在控制器和视图之间传递轻量级数据。
  2. TempData :适用于在多个请求之间临时存储数据(通常用于重定向)。
  3. 强类型模型传递 :通过将模型类传递给视图,实现类型安全的数据绑定。

示例:通过强类型模型传值

1. 定义模型类
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
2. 控制器中传递模型
public IActionResult ProductDetails()
{
    var product = new Product { Id = 1, Name = "笔记本电脑", Price = 8999.99m };
    return View(product);
}
3. 视图中接收模型( ProductDetails.cshtml
@model YourNamespace.Models.Product

<h2>@Model.Name</h2>
<p>价格:@Model.Price</p>
  • @model 指令指定视图使用的模型类型。
  • 使用 @Model 访问模型属性,实现类型安全绑定。

7.3 模型绑定与验证机制

模型绑定(Model Binding)是 ASP.NET MVC 自动将 HTTP 请求数据(如表单、查询字符串、路由参数)映射到控制器方法参数或模型对象的过程。

示例:模型绑定与验证

1. 定义模型并添加验证特性
public class User
{
    [Required(ErrorMessage = "姓名不能为空")]
    [StringLength(50, MinimumLength = 2)]
    public string Name { get; set; }

    [EmailAddress(ErrorMessage = "邮箱格式不正确")]
    public string Email { get; set; }
}
2. 控制器方法中使用模型绑定与验证
[HttpPost]
public IActionResult Register(User user)
{
    if (ModelState.IsValid)
    {
        // 保存用户逻辑
        return RedirectToAction("Success");
    }

    // 若验证失败,返回原视图并显示错误
    return View(user);
}
3. 视图中显示验证错误信息
@model YourNamespace.Models.User

<form asp-action="Register" method="post">
    <div>
        <label asp-for="Name"></label>
        <input asp-for="Name" />
        <span asp-validation-for="Name"></span>
    </div>
    <div>
        <label asp-for="Email"></label>
        <input asp-for="Email" />
        <span asp-validation-for="Email"></span>
    </div>
    <button type="submit">注册</button>
</form>
  • asp-for 是 Tag Helper,用于绑定模型属性。
  • asp-validation-for 显示特定字段的验证错误。

7.4 Web API设计原则与RESTful规范

Web API 是构建 HTTP 服务的框架,适用于前后端分离架构。ASP.NET Core Web API 支持构建符合 RESTful 规范的接口。

RESTful API 设计原则包括:

原则 说明
使用名词复数 /api/products 而非 /api/product
使用HTTP方法 GET(获取)、POST(创建)、PUT(更新)、DELETE(删除)
状态码 200(OK)、201(Created)、400(Bad Request)、404(Not Found)、500(Internal Server Error)

示例:创建一个简单的 Web API 控制器

[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
    private static List<Product> products = new List<Product>
    {
        new Product { Id = 1, Name = "手机", Price = 3999.00m },
        new Product { Id = 2, Name = "平板", Price = 2999.00m }
    };

    [HttpGet]
    public IActionResult GetAll()
    {
        return Ok(products);
    }

    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        var product = products.FirstOrDefault(p => p.Id == id);
        if (product == null)
            return NotFound();
        return Ok(product);
    }

    [HttpPost]
    public IActionResult Create(Product product)
    {
        products.Add(product);
        return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
    }
}
  • [ApiController] 标记该控制器用于 Web API。
  • [Route] 设置默认路由模板。
  • Ok() NotFound() CreatedAtAction() 返回标准 HTTP 响应。

7.5 前后端分离架构下的接口设计与测试

在前后端分离架构中,前端通常使用如 Vue.js、React、Angular 等框架,后端提供 Web API 接口。

接口测试工具推荐:

工具 说明
Postman 图形化接口测试工具,支持请求构造与响应查看
Swagger (OpenAPI) 自动生成 API 文档并提供交互式测试界面
curl 命令行工具,适合自动化测试或调试

使用 Swagger 配置 API 文档

1. 安装 NuGet 包
dotnet add package Swashbuckle.AspNetCore
2. 配置 Startup.cs Program.cs
// Program.cs (ASP.NET Core 6+)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();
3. 启动应用后访问:
https://localhost:<port>/swagger

将看到自动生成的 API 接口文档,并可以直接发起测试请求。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《.NET程序员面试秘笈》是一本专为.NET开发者量身打造的面试指南,内容涵盖.NET框架核心知识、C#高级特性、数据库操作、Web开发、多线程编程及软件工程实践等多个方面。本书结合理论与实战,帮助读者系统复习技术要点,掌握常见面试问题应对策略,提升面试成功率,适用于.NET Framework与.NET Core开发者。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值