zoukankan      html  css  js  c++  java
  • 单元测试:使用xUnit对ASP.NET Core进行单元测试

    一、前言

    在以前的.NET Framework项目中,我们也写过一些单元测试的项目,而在ASP.NET Core 这种Web或者API应用程序中要做单元测试是很方便的。

    这篇文章主要讲解如何使用xUnit对ASP.NET Core应用程序做单元测试。.NET Core中常用的测试工具还有NUnit和MSTest。

    xUnit是一个测试框架,可以针对.net/.net core项目进行测试。测试项目需要引用被测试的项目,从而对其进行测试。测试项目同时需要引用xUnit库。测试编写好后,用Test Runner来运行测试。Test Runner可以读取测试代码,并且会知道我们所使用的测试框架,然后执行,并显示结果。目前可用的Test Runner包括vs自带的Test Explorer,或者dotnet core命令行,以及第三方工具,例如resharper等。

    xUnit可以支持多种平台的测试:

    • .NET Framework
    • .NET Core
    • .NET Standard
    • UWP
    • Xamarin

    二、创建示例项目

    为了使示例项目更加的贴近真实的项目开发,这里采用分层的方式创建一个示例项目,创建完成后的项目结构如下图所示:

     下面讲解一下每层的作用,按照从上往下的顺序:

    1. TestDemo:从名字就可以看出来,这是一个单元测试的项目,针对控制器进行测试。
    2. UnitTest.Data:数据访问,封装与EntityFrameworkCore相关的操作。
    3. UnitTest.IRepository:泛型仓储接口,封装基础的增删改查。
    4. UnitTest.Model:实体层,定义项目中使用到的所有实体。
    5. UnitTest.Repository:泛型仓储接口实现层,实现接口里面定义的方法。
    6. UnitTestDemo:ASP.NET Core WebApi,提供API接口。

    1、UnitTest.Model

    实体层里面只有一个Student类:

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace UnitTest.Model
    {
        public class Student
        {
            public int ID { get; set; }
    
            public string Name { get; set; }
    
            public int Age { get; set; }
    
            public string Gender { get; set; }
        }
    }

    2、UnitTest.Data

     里面封装与EF Core有关的操作,首先需要引入Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools三个NuGet包,直接在管理NuGet程序包里面引入,这里不在讲述。

    引入相关NuGet包以后,我们创建数据上下文类,该类继承自EF Core的DbContext,里面设置表名和一些属性:

    using Microsoft.EntityFrameworkCore;
    using UnitTest.Model;
    
    namespace UnitTest.Data
    {
        /// <summary>
        /// 数据上下文类
        /// </summary>
        public class AppDbContext : DbContext
        {
            /// <summary>
            /// 通过构造函数给父类构造传参
            /// </summary>
            /// <param name="options"></param>
            public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
            {
    
            }
    
            public DbSet<Student> Students { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Student>().ToTable("T_Student");
                modelBuilder.Entity<Student>().HasKey(p => p.ID);
                modelBuilder.Entity<Student>().Property(p => p.Name).HasMaxLength(32);
    
                // 添加种子数据
                modelBuilder.Entity<Student>().HasData(
                    new Student()
                    {
                        ID = 1,
                        Name = "测试1",
                        Age = 20,
                        Gender = ""
                    },
                    new Student()
                    {
                        ID = 2,
                        Name = "测试2",
                        Age = 22,
                        Gender = ""
                    },
                    new Student()
                    {
                        ID = 3,
                        Name = "测试3",
                        Age = 23,
                        Gender = ""
                    });
                base.OnModelCreating(modelBuilder);
            }
        }
    }

    这里采用数据迁移的方式生成数据库,需要在API项目中引入Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools三个NuGet包。引入方式同上。

    然后在API项目的appsettings.json文件里面添加数据库链接字符串:

    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*",
      // 数据库连接字符串
      "ConnectionString": {
        "DbConnection": "Initial Catalog=TestDb;User Id=sa;Password=1234;Data Source=.;Connection Timeout=10;"
      }
    }

    在JSON文件中添加完连接字符串以后,修改Startup类的ConfigureServices方法,在里面配置使用在json文件中添加的连接字符串:

    // 添加数据库连接字符串
    services.AddDbContext<AppDbContext>(options => 
    {
        options.UseSqlServer(Configuration.GetSection("ConnectionString").GetSection("DbConnection").Value);
    });

    这样就可以使用数据迁移的方式生成数据库了。 

    3、UnitTest.IRepository

    该项目中使用泛型仓储,定义一个泛型仓储接口:

    using System.Collections.Generic;
    using System.Threading.Tasks;
    
    namespace UnitTest.IRepository
    {
        public interface IRepository<T> where T:class,new()
        {
            Task<List<T>> GetList();
    
            Task<int?> Add(T entity);
    
            Task<int?> Update(T entity);
    
            Task<int?> Delete(T entity);
        }
    }

    然后在定义IStudentRepository接口继承自IRepository泛型接口:

    using UnitTest.Model;
    
    namespace UnitTest.IRepository
    {
        public interface IStudentRepository: IRepository<Student>
        {
        }
    } 

    4、UnitTest.Repository

     这里是实现上面定义的仓储接口:

    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using UnitTest.Data;
    using UnitTest.IRepository;
    using UnitTest.Model;
    
    namespace UnitTest.Repository
    {
        public class StudentRepository : IStudentRepository
        {
            private readonly AppDbContext _dbContext;
    
            /// <summary>
            /// 通过构造函数实现依赖注入
            /// </summary>
            /// <param name="dbContext"></param>
            public StudentRepository(AppDbContext dbContext)
            {
                _dbContext = dbContext;
            }
    
            public async Task<int?> Add(Student entity)
            {
                _dbContext.Students.Add(entity);
                return await _dbContext.SaveChangesAsync();
            }
    
            public async Task<int?> Delete(Student entity)
            {
                _dbContext.Students.Remove(entity);
                return await _dbContext.SaveChangesAsync();
            }
    
            public async Task<List<Student>> GetList()
            {
                List<Student> list = new List<Student>();
    
                list = await Task.Run<List<Student>>(() => 
                {
                    return _dbContext.Students.ToList();
                });
              
                return list;
            }
    
            public async Task<int?> Update(Student entity)
            {
                Student student = _dbContext.Students.Find(entity.ID);
                if (student != null)
                {
                    student.Name = entity.Name;
                    student.Age = entity.Age;
                    student.Gender = entity.Gender;
                    _dbContext.Entry<Student>(student).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
                    return await _dbContext.SaveChangesAsync();
                }
                return 0;
            }
        }
    }

    5、UnitTestDemo

    先添加一个Value控制器,里面只有一个Get方法,而且没有任何的依赖关系,先进行最简单的测试:

    using Microsoft.AspNetCore.Mvc;
    
    namespace UnitTestDemo.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class ValueController : ControllerBase
        {
            [HttpGet("{id}")]
            public ActionResult<string> Get(int id)
            {
                return $"Para is {id}";
            }
        }
    } 

    6、TestDemo

    我们在添加测试项目的时候,直接选择使用xUnit测试项目,如下图所示:

    这样项目创建完成以后,就会自动添加xUnit的引用:

      <ItemGroup>
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
        <PackageReference Include="xunit" Version="2.4.1" />
        <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
      </ItemGroup>

     但要测试 ASP.NET Core 应用还需要添加两个 NuGet 包:

    Install-Package Microsoft.AspNetCore.App
    Install-Package Microsoft.AspNetCore.TestHost

    上面是使用命令的方式进行安装,也可以在管理NuGet程序包里面进行搜索,然后安装。

    千万不要忘记还要引入要测试的项目。最后的项目引入是这样的:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>
    
        <IsPackable>false</IsPackable>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.8" />
        <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.2" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
        <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
        <PackageReference Include="xunit" Version="2.4.1" />
        <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
        <PackageReference Include="coverlet.collector" Version="1.0.1" />
      </ItemGroup>
    
      <ItemGroup>
        <ProjectReference Include="..UnitTest.ModelUnitTest.Model.csproj" />
        <ProjectReference Include="..UnitTestDemoUnitTestDemo.csproj" />
      </ItemGroup>
    
    </Project>

    都添加完以后,重新编译项目,保证生成没有错误。

    三、编写单元测试

    单元测试按照从上往下的顺序,一般分为三个阶段:

    1. Arrange:准备阶段。这个阶段做一些准备工作,例如创建对象实例,初始化数据等。
    2. Act:行为阶段。这个阶段是用准备好的数据去调用要测试的方法。
    3. Assert:断定阶段。这个阶段就是把调用目标方法的返回值和预期的值进行比较,如果和预期值一致则测试通过,否则测试失败。

    我们在API项目中添加了一个Value控制器,我们以Get方法作为测试目标。一般一个单元测试方法就是一个测试用例。

    我们在测试项目中添加一个ValueTest测试类,然后编写一个单元测试方法,这里是采用模拟HTTPClient发送Http请求的方式进行测试:

    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using UnitTestDemo;
    using Xunit;
    
    namespace TestDemo
    {
        public class ValueTests
        {
            public HttpClient _client { get; }
    
            /// <summary>
            /// 构造方法
            /// </summary>
            public ValueTests()
            {
                var server = new TestServer(WebHost.CreateDefaultBuilder()
               .UseStartup<Startup>());
                _client = server.CreateClient();
            }
    
            [Fact]
            public async Task GetById_ShouldBe_Ok()
            {
                // 1、Arrange
                var id = 1;
    
                // 2、Act
                // 调用异步的Get方法
                var response = await _client.GetAsync($"/api/value/{id}");
    
                // 3、Assert
                Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    
            }
        }
    }

    我们在构造函数中,通过TestServer拿到一个HttpClient对象,用它来模拟Http请求。我们写了一个测试用例,完整演示了单元测试的Arrange、Act和Assert三个步骤。

    1、运行单元测试

    单元测试用例写好以后,打开“测试资源管理器”:

    在底部就可以看到测试资源管理器了:

    在要测试的方法上面右键,选择“运行测试”就可以进行测试了:

    注意观察测试方法前面图标的颜色,目前是蓝色的,表示测试用例还没有运行过:

    测试用例结束以后,我们在测试资源管理器里面可以看到结果:

     绿色表示测试通过。我们还可以看到执行测试用例消耗的时间。

    如果测试结果和预期结果一致,那么测试用例前面图标的颜色也会变成绿色:

    如果测试结果和预期结果不一致就会显示红色,然后需要修改代码直到出现绿色图标。我们修改测试用例,模拟测试失败的情况:

    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using UnitTestDemo;
    using Xunit;
    
    namespace TestDemo
    {
        public class ValueTests
        {
            public HttpClient _client { get; }
    
            /// <summary>
            /// 构造方法
            /// </summary>
            public ValueTests()
            {
                var server = new TestServer(WebHost.CreateDefaultBuilder()
               .UseStartup<Startup>());
                _client = server.CreateClient();
            }
    
            [Fact]
            public async Task GetById_ShouldBe_Ok()
            {
                // 1、Arrange
                var id = 1;
    
                // 2、Act
                // 调用异步的Get方法
                var response = await _client.GetAsync($"/api/value/{id}");
    
                //// 3、Assert
                //Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    
                // 3、Assert
                // 模拟测试失败
                Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    
            }
        }
    }

    然后运行测试用例:

    2、调试单元测试

    我们也可以通过添加断点的方式在测试用例中进行调试。调试单元测试很简单,只需要在要调试的方法上面右键选择“调试测试”,如下图所示:

    其它操作就跟调试普通方法一样。

    除了添加断点调试,我们还可以采用打印日志的方法来快速调试,xUnit可以很方便地做到这一点。我们修改ValueTest类:

    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using UnitTestDemo;
    using Xunit;
    using Xunit.Abstractions;
    
    namespace TestDemo
    {
        public class ValueTests
        {
            public HttpClient _client { get; }
            public ITestOutputHelper Output { get; }
    
            /// <summary>
            /// 构造方法
            /// </summary>
            public ValueTests(ITestOutputHelper outputHelper)
            {
                var server = new TestServer(WebHost.CreateDefaultBuilder()
               .UseStartup<Startup>());
                _client = server.CreateClient();
                Output = outputHelper;
            }
    
            [Fact]
            public async Task GetById_ShouldBe_Ok()
            {
                // 1、Arrange
                var id = 1;
    
                // 2、Act
                // 调用异步的Get方法
                var response = await _client.GetAsync($"/api/value/{id}");
    
                // 3、Assert
                // 模拟测试失败
                //Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    
                // 输出返回信息
                // Output
                var responseText = await response.Content.ReadAsStringAsync();
                Output.WriteLine(responseText);
    
                // 3、Assert
                Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    
            }
        }
    }

    这里我们在构造函数中添加了 ITestOutputHelper 参数,xUnit 会将一个实现此接口的实例注入进来。拿到这个实例后,我们就可以用它来输出日志了。运行(注意不是 Debug)此方法,运行结束后在测试资源管理器里面查看:

     点击就可以看到输出的日志了:

    在上面的例子中,我们是使用的简单的Value控制器进行测试,控制器里面没有其他依赖关系,如果控制器里面有依赖关系该如何测试呢?方法还是一样的,我们新建一个Student控制器,里面依赖IStudentRepository接口,代码如下:

    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using UnitTest.IRepository;
    using UnitTest.Model;
    
    namespace UnitTestDemo.Controllers
    {
        [Route("api/student")]
        [ApiController]
        public class StudentController : ControllerBase
        {
            private readonly IStudentRepository _repository;
    
            /// <summary>
            /// 通过构造函数注入
            /// </summary>
            /// <param name="repository"></param>
            public StudentController(IStudentRepository repository)
            {
                _repository = repository;
            }
    
            /// <summary>
            /// get方法
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            public async Task<ActionResult<List<Student>>> Get()
            {
                return await _repository.GetList();
            }
        }
    }

    然后在Startup类的ConfigureServices方法中注入:

    public void ConfigureServices(IServiceCollection services)
    {
        // 添加数据库连接字符串
        services.AddDbContext<AppDbContext>(options => 
        {
            options.UseSqlServer(Configuration.GetSection("ConnectionString").GetSection("DbConnection").Value);
        });
        // 添加依赖注入到容器中
        services.AddScoped<IStudentRepository, StudentRepository>();
        services.AddControllers();
    }

    在单元测试项目中添加StudentTest类:

    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using Newtonsoft.Json;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Threading.Tasks;
    using UnitTest.Model;
    using UnitTestDemo;
    using Xunit;
    using Xunit.Abstractions;
    
    namespace TestDemo
    {
        public class StudentTest
        {
            public HttpClient Client { get; }
            public ITestOutputHelper Output { get; }
            public StudentTest(ITestOutputHelper outputHelper)
            {
                var server = new TestServer(WebHost.CreateDefaultBuilder()
               .UseStartup<Startup>());
                Client = server.CreateClient();
                Output = outputHelper;
            }
    
            [Fact]
            public async Task Get_ShouldBe_Ok()
            {
                // 2、Act
                var response = await Client.GetAsync($"api/student");
    
                // Output
                string context = await response.Content.ReadAsStringAsync();
                Output.WriteLine(context);
                List<Student> list = JsonConvert.DeserializeObject<List<Student>>(context);
    
                // Assert
                Assert.Equal(3, list.Count);
            }
        }
    }

    然后运行单元测试:

    可以看到,控制器里面如果有依赖关系,也是可以使用这种方式进行测试的。

    Post方法也可以使用同样的方式进行测试,修改控制器,添加Post方法:

    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using UnitTest.IRepository;
    using UnitTest.Model;
    
    namespace UnitTestDemo.Controllers
    {
        [Route("api/student")]
        [ApiController]
        public class StudentController : ControllerBase
        {
            private readonly IStudentRepository _repository;
    
            /// <summary>
            /// 通过构造函数注入
            /// </summary>
            /// <param name="repository"></param>
            public StudentController(IStudentRepository repository)
            {
                _repository = repository;
            }
    
            /// <summary>
            /// get方法
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            public async Task<ActionResult<List<Student>>> Get()
            {
                return await _repository.GetList();
            }
    
            /// <summary>
            /// Post方法
            /// </summary>
            /// <param name="entity"></param>
            /// <returns></returns>
            [HttpPost]
            public async Task<bool> Post([FromBody]Student entity)
            {
                int? result = await _repository.Add(entity);
                if(result==null)
                {
                    return false;
                }
                else
                {
                    return result > 0 ? true : false;
                }
                
            }
        }
    }

    在增加一个Post的测试方法:

    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using Newtonsoft.Json;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Threading.Tasks;
    using UnitTest.Model;
    using UnitTestDemo;
    using Xunit;
    using Xunit.Abstractions;
    
    namespace TestDemo
    {
        public class StudentTest
        {
            public HttpClient Client { get; }
            public ITestOutputHelper Output { get; }
            public StudentTest(ITestOutputHelper outputHelper)
            {
                var server = new TestServer(WebHost.CreateDefaultBuilder()
               .UseStartup<Startup>());
                Client = server.CreateClient();
                Output = outputHelper;
            }
    
            [Fact]
            public async Task Get_ShouldBe_Ok()
            {
                // 2、Act
                var response = await Client.GetAsync($"api/student");
    
                // Output
                string context = await response.Content.ReadAsStringAsync();
                Output.WriteLine(context);
                List<Student> list = JsonConvert.DeserializeObject<List<Student>>(context);
    
                // Assert
                Assert.Equal(3, list.Count);
            }
    
            [Fact]
            public async Task Post_ShouldBe_Ok()
            {
                // 1、Arrange
                Student entity = new Student()
                {
                 Name="测试9",
                 Age=25,
                 Gender=""
                };
    
                var str = JsonConvert.SerializeObject(entity);
                HttpContent content = new StringContent(str);
    
                // 2、Act
                content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
    
                HttpResponseMessage response = await Client.PostAsync("api/student", content);
                string responseBody = await response.Content.ReadAsStringAsync();
                Output.WriteLine(responseBody);
    
                // 3、Assert
                Assert.Equal("true", responseBody);
            }
        }
    }

    运行测试用例:

    这样一个简单的单元测试就完成了。

    我们观察上面的两个测试类,发现这两个类都有一个共同的特点:都是在构造函数里面创建一个HttpClient对象,我们可以把创建HttpClient对象抽离到一个共同的基类里面,所有的类都继承自基类。该基类代码如下:

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using System.IO;
    using System.Net.Http;
    using UnitTestDemo;
    
    namespace TestDemo
    {
        /// <summary>
        /// 基类
        /// </summary>
        public class ApiControllerTestBase
        {
            /// <summary>
            /// 返回HttpClient对象
            /// </summary>
            /// <returns></returns>
            protected HttpClient GetClient()
            {
                var builder = new WebHostBuilder()
                                    // 指定使用当前目录
                                    .UseContentRoot(Directory.GetCurrentDirectory())
                                    // 使用Startup类作为启动类
                                    .UseStartup<Startup>()
                                    // 设置使用测试环境
                                    .UseEnvironment("Testing");
                var server = new TestServer(builder);
                // 创建HttpClient
                HttpClient client = server.CreateClient();
    
                return client;
            }
        }
    }

    然后修改StudentTest类,使该类继承自上面创建的基类:

    using Newtonsoft.Json;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Threading.Tasks;
    using UnitTest.Model;
    using Xunit;
    using Xunit.Abstractions;
    
    namespace TestDemo
    {
        public class StudentTest: ApiControllerTestBase
        {
            public HttpClient Client { get; }
            public ITestOutputHelper Output { get; }
    
    
            public StudentTest(ITestOutputHelper outputHelper)
            {
                // var server = new TestServer(WebHost.CreateDefaultBuilder()
                //.UseStartup<Startup>());
                // Client = server.CreateClient();
    
                // 从父类里面获取HttpClient对象
                Client = base.GetClient();
                Output = outputHelper;
            }
    
            [Fact]
            public async Task Get_ShouldBe_Ok()
            {
                // 2、Act
                var response = await Client.GetAsync($"api/student");
    
                // Output
                string context = await response.Content.ReadAsStringAsync();
                Output.WriteLine(context);
                List<Student> list = JsonConvert.DeserializeObject<List<Student>>(context);
    
                // Assert
                Assert.Equal(3, list.Count);
            }
    
            [Fact]
            public async Task Post_ShouldBe_Ok()
            {
                // 1、Arrange
                Student entity = new Student()
                {
                 Name="测试9",
                 Age=25,
                 Gender=""
                };
    
                var str = JsonConvert.SerializeObject(entity);
                HttpContent content = new StringContent(str);
    
                // 2、Act
                content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
    
                HttpResponseMessage response = await Client.PostAsync("api/student", content);
                string responseBody = await response.Content.ReadAsStringAsync();
                Output.WriteLine(responseBody);
    
                // 3、Assert
                Assert.Equal("true", responseBody);
            }
        }
    }

    文章中的示例代码地址:git@github.com:jxl1024/UnitTest.git

    参考:https://www.cnblogs.com/willick/p/aspnetcore-unit-tests-with-xunit.html 

  • 相关阅读:
    SVN还原项目到某一版本(转)
    C# Web Service 不使用服务引用直接调用方法(转)
    动态调用webservice时 ServiceDescriptionImporter类在vs2010无法引用的解决方法 (转)
    log4net示例2-日志输入存入Access(转)
    C# log4net 配置及使用详解--日志保存到文件和Access(转)
    未能解析引用的程序集......因为它对不在当前目标框架“.NETFramework,Version=v4.0,Profile=Client”中的 (转)
    Hello log4net——做一个实用好用的log4net的demo(转)
    JS移动客户端--触屏滑动事件
    js生成二维码实例
    触屏版类似刷新页面文本框获取焦点的同时弹出手机键盘的做法
  • 原文地址:https://www.cnblogs.com/dotnet261010/p/12498191.html
Copyright © 2011-2022 走看看