官方資料: "https://github.com/dotnet/core" "https://docs.microsoft.com/en us/aspnet/core" "https://docs.microsoft.com/en us/ef/core" 相關文章: "ASP.NET 5 RC1 ...
官方資料:
- https://github.com/dotnet/core
- https://docs.microsoft.com/en-us/aspnet/core
- https://docs.microsoft.com/en-us/ef/core
相關文章:ASP.NET 5 RC1 升級 ASP.NET Core 1.0 RC2 記錄
ASP.NET Core 1.0 更新比較快(可能後面更新就不大了),閱讀註意時間節點,這篇博文主要記錄用 ASP.NET Core 1.0 開發簡單應用項目的一些記錄,以備查閱。
ASP.NET Core 1.0 相關 Nuget 程式包源:https://api.nuget.org/v3/index.json
閱讀目錄:
- 理解 .NET Platform Standard
- Startup 配置
- Sample.BootStrapper.Startup 配置
- ASP.NET Core 1.0 Startup 配置
- UnitTest 單元測試
- 類庫項目單元測試
- WebApi 項目單元測試
- Microsoft.EntityFrameworkCore
- 基本配置(MySql)
- ModelBuilderExtenions 擴展
- 記錄執行 SQL
- EntityFrameworkCore 遷移
- CLI 命令
1. 理解 .NET Platform Standard
參考文章:
- 理解 .NET Platform Standard
- Running .NET Core apps on multiple frameworks and What the Target Framework Monikers (TFMs) are about
- .NET Platform Standard
在用 ASP.NET Core 1.0 開發之前,理解 .NET Platform Standard(.NET 平臺標準)是非常有必要的,因為部署是跨平臺的,ASP.NET Core 1.0 應用程式的配置不同,部署環境也會不同,並且項目之間的相容也會存在一些問題。
.NET Platform Standard 列表(2016 上半年):
Target Platform Name | Alias | |||||||
---|---|---|---|---|---|---|---|---|
.NET Platform Standard | netstandard | 1.0 | 1.1 | 1.2 | 1.3 | 1.4 | 1.5 | 1.6 |
.NET Core | netcoreapp | → | → | → | → | → | → | 1.0 |
.NET Framework | net | → | → | → | → | → | → | 4.6.3 |
→ | → | → | → | → | 4.6.2 | |||
→ | → | → | → | 4.6.1 | ||||
→ | → | → | 4.6 | |||||
→ | → | 4.5.2 | ||||||
→ | → | 4.5.1 | ||||||
→ | 4.5 | |||||||
Universal Windows Platform | uap | → | → | → | → | 10.0 | ||
Windows | win | → | → | 8.1 | ||||
→ | 8.0 | |||||||
Windows Phone | wpa | → | → | 8.1 | ||||
Windows Phone Silverlight | wp | 8.1 | ||||||
8.0 | ||||||||
Mono/Xamarin Platforms | → | → | → | → | → | → | * | |
Mono | → | → | * |
上面圖表時間雖然有點老,但和現在的發展是一樣的,只不過版本有所更新,主要的三個平臺:
- .NET Platform Standard(netstandard):.NET 平臺標準,或者稱為 .NET 通用平臺,是 .NET 部署所有平臺的一種通用標準規範(包含 Windows、Mac、Linux 等),建議使用,現在最新版本是 1.6 (2.0 版本快發佈)。
- .NET Core(netcoreapp):.NET Core SDK running on CoreCLR/CoreFx,簡單來說,可以跑在安裝 CoreCLR/CoreFx 的任何平臺上(包含 Windows、Mac、Linux 等),前提必須是有 .NET Core 的運行環境,建議使用,現在最新版本是 1.1.0
- .NET Framework(net):.Net Framework SDK running on Desktop CLR / Full BCL and FCL,簡單來說,只能跑在 Windows 平臺上,不建議使用,最新版本是 4.6.3。
我們在開發 ASP.NET Core 1.0 應用程式的時候,一般是選擇netstandard
或netcoreapp
版本,並且這兩個平臺版本是相互相容的,分別看一下示例:
netstandard1.6
平臺:
{
"version": "1.0.0",
"dependencies": {
"Microsoft.EntityFrameworkCore": "1.0.0",
"NETStandard.Library": "1.6.1"
},
"frameworks": {
"netstandard1.6": {
"imports": "dnxcore50"
}
}
}
"imports": "dnxcore50"
的意思是相容之前dnxcore50
平臺(dnxcore50
現在已被廢棄,並被netcoreapp
取代),比如你之前有個類庫是用dnxcore50
平臺開發的,現在使用netstandard
平臺開發的類庫,如果不添加此配置,是會報錯的。"NETStandard.Library": "1.6.1"
包含了一系列和netstandard
相關的 Nuget 程式包,配置netstandard
平臺,必須添加此配置,如果沒有的話,.NET 基礎的命名空間是訪問不到的。
netcoreapp1.1.0
平臺:
{
"version": "1.0.0",
"dependencies": {
"Microsoft.EntityFrameworkCore": "1.0.0"
},
"frameworks": {
"netcoreapp1.1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
}
},
"imports": [
"dotnet5.6",
"portable-net45+win10"
]
}
}
}
netstandard1.1.0
平臺和上面netstandard1.6
平臺配置差不多,Microsoft.NETCore.App 和 NETStandard.Library 是一樣目的,dotnet5.6
和dnxcore50
一樣,都是 .NET Core 之前平臺的廢棄代號。
portable
的意思是便攜型,如果添加了此配置,表示應用程式發佈部署不攜帶依賴的程式包,而是使用系統中安裝配置的。
還有一點需要註意的是,netcoreapp1.1.0
平臺和netstandard1.6
平臺開發是相互相容的,比如我們開發一些自己的 Nuget 程式包,使用的平臺版本可以是上面兩個,但最好是使用netstandard
低一點的版本,因為以後netstandard
可能是微軟平臺的一種標準規範,比如我現在開發 Nuget 程式包平臺使用netcoreapp1.1.0
,ASP.NET Core 1.0 應用程式使用netcoreapp1.1.0
平臺,後者就可以引用前者,因為第三方 Nuget 程式包使用平臺版本可能比較低,所以我們開發的 ASP.NET Core 1.0 應用程式平臺版本也相應低一些。
2. Startup 配置
Startup 需要添加額外的配置,以便於單元測試的進行,比如 EF、依賴註入和 AutoMapper 配置,需要獨立進行配置,而不是放在 ASP.NET Core 1.0 應用程式中的 Startup 配置中。
2.1 Sample.BootStrapper.Startup 配置
比如這樣的一個項目 Sample.BootStrapper:
public static class Startup
{
public static void Configure(this IServiceCollection services, string connectionString)
{
services.AddDbContext<SampleDbContext>(options =>
options.UseMySQL(connectionString, b => b.MigrationsAssembly("Sample.WebApi"))
);
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddScoped<IDbContext, CommodityDbContext>();
ConfigureMapper();
}
private static void ConfigureMapper()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SupplierItem, PurchaseItemDTO>()
.ForMember(d => d.ItemDetailDTOs, opt => opt.MapFrom(s => s.SupplierItemSkus));
cfg.CreateMap<SupplierItemSku, PurchaseItemDetailDTO>();
});
}
}
project.json 配置:
{
"version": "1.0.0-*",
"dependencies": {
"System.ComponentModel.Primitives": "4.1.0",
"Microsoft.NETCore.App": ""
},
"frameworks": {
"netcoreapp1.1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
}
}
}
}
上面代碼主要是對 IServiceCollection 的 Configure 方法進行了擴展,這樣對 Domain、Repository 和 AppliactionService 進行獨立的單元測試了,只需要在構造函數中進行添加調用即可,後面再說這一點。
2.2 ASP.NET Core 1.0 Startup 配置
另外,ASP.NET Core 1.0 應用程式的 Startup 示例代碼:
public class Startup
{
public Startup(IHostingEnvironment env)
{
#if DEBUG
var path = Directory.GetCurrentDirectory();
#else
var path = env.ContentRootPath;
#endif
var builder = new ConfigurationBuilder()
.SetBasePath(path)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Error()
.WriteTo.RollingFile(Path.GetFullPath("logs/log-{Date}.txt"))
.CreateLogger();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
// 配置 WebApi 返回 Json 數據大小寫問題,預設數據會首字母小寫。
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
// 註意這裡
services.Configure(Configuration["data:ConnectionString"]);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddSerilog();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseStaticFiles();
app.UseMvc();
}
}
日誌服務使用的是 Serilog,日誌級別有多種進行選擇(可以 Debug、Error 等),日誌和連接字元串的配置可以放在 appsettings.json 文件中,ConfigureServices 裡面調用 Sample.BootStrapper.StartUp 的配置。
project.json 示例代碼:
{
"dependencies": {
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Routing": "1.0.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"Microsoft.AspNetCore.Cors": "1.0.0",
"Serilog": "2.3.0",
"Serilog.Extensions.Logging":"1.3.1",
"Serilog.Sinks.RollingFile": "3.2.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Sample.BootStrapper": "1.0.0-*",
"Microsoft.EntityFrameworkCore.Design": {
"type": "build",
"version": "1.0.0-preview2-final"
}
},
"tools": {
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
}
},
"imports": [
"dotnet5.6",
"portable-net45+win10"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"**/*.cshtml",
"appsettings.json",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}
appsettings.json 示例代碼:
{
"data": {
"ConnectionString": ""
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Error",
"System": "Information",
"Microsoft": "Information"
}
}
}
另外,在 Program.cs 中可以配置選擇伺服器,比如 IIS 或 Kestrel 等。
3. UnitTest 單元測試
相關文章:
單元測試主要包含兩方面:類庫項目單元測試和 WebApi 項目單元測試。
3.1 類庫項目單元測試
類庫項目單元測試代碼示例:
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
using Sample.BootStrapper;
public class UnitTest
{
private readonly ITestOutputHelper output;
private IServiceProvider provider;
private IProductRepository _productRepository;
public UnitTest(ITestOutputHelper output)
{
var connectionString = "";
var services = new ServiceCollection();
this.output = output;
services.Configure(connectionString);
provider = services.BuildServiceProvider();
_productRepository = provider.GetService<IProductRepository>();
}
[Fact]
public async Task GetTest()
{
Assert.True(true);
}
}
provider.GetService 手動獲取依賴註入的對象,ITestOutputHelper 的目的是取代Console.WriteLine
(因為不支持),但現在 ITestOutputHelper 好像也輸出不了,之前 RC2 的時候是可以的,不知道啥原因。
3.1 WebApi 項目單元測試
WebApi 項目單元測試示例代碼:
using Xunit.Abstractions;
using Microsoft.AspNetCore.TestHost;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Sample.WebApi;
public class UnitTest
{
private readonly ITestOutputHelper output;
private readonly TestServer _server;
private readonly HttpClient _client;
public UnitTest(ITestOutputHelper output)
{
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task GetTest()
{
var ids = new List<int>{1, 2};
var httpContent = new StringContent(JsonConvert.SerializeObject(ids), Encoding.UTF8, "application/json");
var response = await _client.PostAsync($"/products", httpContent);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
RC2 版本是無法對 WebApi 進行單元測試的,因為 TestHost 不支持,現在 ASP.NET Core 1.0 版本和 ASP.NET WebApi 2 是差不多的了,使用也很方便,不過 HttpClient 沒有了 PostAsJsonAsync 方法,需要使用 JsonConvert 手動轉換一下。
project.json 配置代碼:
{
"version": "1.0.0-*",
"testRunner": "xunit",
"dependencies": {
"xunit": "2.2.0-beta4-build3444",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.AspNetCore.TestHost": "1.0.0",
"Microsoft.Extensions.DependencyInjection": "1.1.0",
"Sample.BootStrapper": "1.0.0-*",
"Sample.WebApi": "1.0.0-*"
},
"frameworks": {
"netcoreapp1.0.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
}
},
"imports": [
"dotnet5.6",
"portable-net45+win10"
]
}
}
}
我之前用netstandard1.6
平臺,但配置 xunit 的時候,發現不支持最新的版本,後來就該用了netcoreapp1.0.1
,testRunner
配置後可以在 Test Explorer 視窗中看到單元測試,也可以在方法的視窗上看到,主要用於調試目的。
單元測試命令:dotnet test
4. Microsoft.EntityFrameworkCore
4.1 基本配置
Microsoft.EntityFrameworkCore 和 EntityFramework 7 的用法差不多,現在項目使用的 MySql 資料庫,示例配置代碼:
public class SampleDbContext : DbContext
{
public SampleDbContext(DbContextOptions<SampleDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.AddEntityConfigurationsFromAssembly(GetType().GetTypeInfo().Assembly);
}
}
Map 映射配置(可能多個):
public class ProductMap : EntityMappingConfiguration<Product>
{
public override void Map(EntityTypeBuilder<Product> builder)
{
builder.HasKey(p => p.Id);
}
}
4.2 ModelBuilderExtenions 擴展
AddEntityConfigurationsFromAssembly 是對 ModelBuilder 的擴展,這樣如果有多個實體映射配置,OnModelCreating 中只需要一行代碼就可以了,擴展代碼:
public interface IEntityMappingConfiguration
{
void Map(ModelBuilder b);
}
public interface IEntityMappingConfiguration<T> : IEntityMappingConfiguration where T : class
{
void Map(EntityTypeBuilder<T> builder);
}
public abstract class EntityMappingConfiguration<T> : IEntityMappingConfiguration<T> where T : class
{
public abstract void Map(EntityTypeBuilder<T> b);
public void Map(ModelBuilder b)
{
Map(b.Entity<T>());
}
}
public static class ModelBuilderExtenions
{
private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
{
return assembly
.GetTypes()
.Where(x =>
!x.GetTypeInfo().IsAbstract &&
x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
}
public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
{
var mappingTypes = assembly.GetMappingTypes(typeof(IEntityMappingConfiguration<>));
foreach (var config in mappingTypes.Select(Activator.CreateInstance).Cast<IEntityMappingConfiguration>())
config.Map(modelBuilder);
}
}
project.json 示例代碼:
{
"version": "1.0.0-*",
"dependencies": {
"MySql.Data.EntityFrameworkCore": "7.0.6-IR31",
"MySql.Data": "7.0.6-IR31",
"System.Reflection.TypeExtensions": "4.3.0",
"Microsoft.Extensions.DependencyInjection": "1.1.0"
},
"frameworks": {
"netcoreapp1.0.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
}
}
}
}
}
EntityFrameworkCore 針對 MySql 微軟沒有開放相應的 Nuget 程式包,第三方有很多的程式包,但或多或少的有些問題,MySql.Data.EntityFrameworkCore 是 EntityFrameworkCore GitHub Issue 中出現最多的,並且是 MySql 自己開發的,所以使用的話沒什麼問題。
如果在查詢的時候報這個錯誤:
System.MissingMethodException was unhandled
HResult=-2146233069
Message=Method not found: 'Void Microsoft.EntityFrameworkCore.Query.QueryContextFactory..ctor(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager, Microsoft.EntityFrameworkCore.Internal.IConcurrencyDetector, Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IChangeDetector)'.
Source=Microsoft.EntityFrameworkCore.Relational
StackTrace:
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalQueryContextFactory..ctor(IStateManager stateManager, IConcurrencyDetector concurrencyDetector, IRelationalConnection connection, IChangeDetector changeDetector)
--- End of stack trace from previous location where exception was thrown ---
解決方式是引用 MySql.Data.EntityFrameworkCore 程式包的類庫,移除 Microsoft.EntityFrameworkCore 程式包,參考資料:MissingMethodException DbSet.ToList
4.3 記錄執行 SQL
另外,關於 EntityFrameworkCore 如何記錄執行 SQL,Google 找了好久也沒找到好的解決方案,最後找到了一個別人寫的 IQueryable 擴展方法:
public static class IQueryableExtensions
{
private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();
private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler");
private static readonly PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider");
private static readonly MethodInfo CreateQueryParserMethod = QueryCompilerTypeInfo.DeclaredMethods.First(x => x.Name == "CreateQueryParser");
private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");
private static readonly FieldInfo QueryCompilationContextFactoryField = typeof(Database).GetTypeInfo().DeclaredFields.Single(x => x.Name == "_queryCompilationContextFactory");
public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
if (!(query is EntityQueryable<TEntity>) && !(query is InternalDbSet<TEntity>))
{
throw new ArgumentException("Invalid query");
}
var queryCompiler = (IQueryCompiler)QueryCompilerField.GetValue(query.Provider);
var nodeTypeProvider = (INodeTypeProvider)NodeTypeProviderField.GetValue(queryCompiler);
var parser = (IQueryParser)CreateQueryParserMethod.Invoke(queryCompiler, new object[] { nodeTypeProvider });
var queryModel = parser.GetParsedQuery(query.Expression);
var database = DataBaseField.GetValue(queryCompiler);
var queryCompilationContextFactory = (IQueryCompilationContextFactory)QueryCompilationContextFactoryField.GetValue(database);
var queryCompilationContext = queryCompilationContextFactory.Create(false);
var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor();
modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
var sql = modelVisitor.Queries.First().ToString();
return sql;
}
}
調用示例:
var query = _productRepository.Get(1);
var sql = query.ToSql();
Console.WriteLine(sql);
不過暫時只能輸出單表查詢的,多表關聯查詢的執行 SQL 輸出不了。
4.4 EntityFrameworkCore 遷移
相關文章:
相對於 EntityFramework 7,EntityFrameworkCore 遷移改變很多,
首先,ASP.NET Core 1.0 project.json 中添加如下配置:
{
"dependencies": {
"Microsoft.EntityFrameworkCore.Design": {
"type": "build",
"version": "1.0.0-preview2-final"
}
},
"tools": {
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0.1": { }
}
}
Sample.BootStrapper.Startup 中 Configure 修改如下:
public static void Configure(this IServiceCollection services, string connectionString)
{
services.AddDbContext<CommodityDbContext>(options =>
options.UseMySQL(connectionString, b => b.MigrationsAssembly("Sample.WebApi"))//添加 MigrationsAssembly
);
///to do...
然後 CMD 直接切換到 Sample.WebApi 文件目錄下,執行如下命令:
dotnet ef migrations add 名稱
dotnet ef database update
5. CLI 命令
相關資料:.NET Core 命令行介面工具
dotnet 具有以下命令:
dotnet new
:初始化 C# 或 F # 控制台應用程式項目。dotnet restore
:還原給定應用程式的依賴項。dotnet build
:生成 .NET Core 應用程式。dotnet publish
:發佈 .NET 可移植或獨立應用程式。dotnet run
:從源運行應用程式。dotnet test
:使用 project.json 中指定的測試運行程式運行測試。dotnet pack
:創建代碼的 NuGet 包。
CLI 還支持持續集成,不過沒試過,我第一次用 dotnet pack
,結合 Nuget Package Explorer 可以很方便的發佈管理程式包。
大概就記錄這些。