ASP.NET Core使用HostingStartup增強啟動操作方法詳解
概念
在ASP.NET Core中我們可以使用一種機制來增強啟動時的操作,它就是HostingStartup。如何叫"增強"操作,相信了解過AOP概念的同學(xué)應(yīng)該都非常的熟悉。我們常說AOP使用了關(guān)注點分離的方式,增強了對現(xiàn)有邏輯的操作。而我們今天要說的HostingStartup就是為了"增強"啟動操作,這種"增強"的操作甚至可以對現(xiàn)有的程序可以做到無改動的操作。例如,外部程序集可通過HostingStartup實現(xiàn)為應(yīng)用提供配置服務(wù)、注冊服務(wù)或中間件管道操作等。
使用方式
HostingStartup屬性表示要在運行時激活的承載啟動程序集。大致分為兩種情況,一種是自動掃描當前Web程序集中通過HostingStartup指定的類,另一種是手動添加配置hostingstartupassembles指定外部的程序集中通過HostingStartup指定的類。第一種方式相對簡單,但是對Web程序本身有入侵,第二種方式稍微復(fù)雜一點點,但是可以做到對現(xiàn)有代碼無入侵操作,接下來我們分別演示這兩種使用方式。
ASP.NET Core中直接定義
首先是在ASP.NET Core程序中直接使用HostingStartup,這種方式比較簡單首先在Web程序中隨便定義一個類,然后實現(xiàn)IHostingStartup接口,最后別忘了在程序集中添加HostingStartupAttribute指定要啟動的類的類型,具體代碼如下所示
using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; //通過HostingStartup指定要啟動的類型 [assembly: HostingStartup(typeof(HostStartupWeb.HostingStartupInWeb))] namespace HostStartupWeb { public class HostingStartupInWeb : IHostingStartup { public void Configure(IWebHostBuilder builder) { //程序啟動時打印依據(jù)話,代表執(zhí)行到了這里 Debug.WriteLine("Web程序中HostingStartupInWeb類啟動"); //可以添加配置 builder.ConfigureAppConfiguration(config => { //模擬添加一個一個內(nèi)存配置 var datas = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("ServiceName", "HostStartupWeb") }; config.AddInMemoryCollection(datas); }); //可以添加ConfigureServices builder.ConfigureServices(services=> { //模擬注冊一個PersonDto services.AddScoped(provider=>new PersonDto { Id = 1, Name = "yi念之間", Age = 18 }); }); //可以添加Configure builder.Configure(app => { //模擬添加一個中間件 app.Use(async (context, next) => { await next(); }); }); } } }
僅僅使用上面所示的這些代碼,便可在Web程序啟動的時候去自動執(zhí)行HostingStartupInWeb的Configure方法,在這里面我們幾乎可以使用所有針對ASP.NET Core程序配置的操作,而且不需要在Web程序中額外添加別的代碼就可以自動調(diào)用HostingStartupInWeb的Configure方法。
外部程序集引入
我們之前也說過,上面的方式雖然使用起來相對簡單一點,僅僅是一點,那就是省去了指定啟動程序集的邏輯。但是,上面的方式需要在Web程序中添加,這樣的話還是會修改代碼。而且,可能更多的時候我們是在外部的程序集中編寫HostingStartup邏輯,這時候就需要使用另一種方式在將外部程序集中引入HostingStartup。首先我們要在自定義的程序集中至少引入Microsoft.AspNetCore.Hosting包才能使用HostingStartup
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
如果你不需要使用注冊中間件的邏輯那么僅僅引入Microsoft.AspNetCore.Hosting.Abstractions即可
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
如果需要使用其他功能包,可以自行在定義的程序集中引入。比如我們定義了一個名為HostStartupLib的Standard類庫,并創(chuàng)建了名為HostStartupLib的類
using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; [assembly: HostingStartup(typeof(HostStartupLib.HostingStartupInLib))] namespace HostStartupLib { public class HostingStartupInLib : IHostingStartup { public void Configure(IWebHostBuilder builder) { Debug.WriteLine("Lib程序中HostingStartupInLib類啟動"); //添加配置 builder.ConfigureAppConfiguration((context, config) => { var datas = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("ServiceName", "HostStartupLib") }; config.AddInMemoryCollection(datas); }); //添加ConfigureServices builder.ConfigureServices(services=> { services.AddScoped(provider=>new PersonDto { Id = 2, Name = "er念之間", Age = 19 }); }); //添加Configure builder.Configure(app => { app.Use(async (context, next) => { await next(); }); }); } } }
然后我們將自定義的HostStartupLib這個Standard類庫引入Web項目中,運行Web程序,發(fā)現(xiàn)HostingStartupInLib的Configure方法并不能被調(diào)用。其實我們上面說過了,將HostingStartup從外部程序集引入的話需要手動指定啟動程序集的名稱。指定啟動程序集的方式有兩種,一種是指定IWebHostBuilder的擴展UseSetting指定
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { //通過UseSetting的方式指定程序集的名稱 webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib"); //如果HostingStartup存在多個程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2 //webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib;HostStartupLib2"); webBuilder.UseStartup<Startup>(); });
另一種通過添加環(huán)境變量ASPNETCORE_HOSTINGSTARTUPASSEMBLIES的方式,可以通過設(shè)置launchSettings.json中
"environmentVariables": { "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib" //如果HostingStartup存在多個程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2 //"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib;HostStartupLib2" }
可以引入多個包含HostingStartup的程序集,在設(shè)置WebHostDefaults.HostingStartupAssembliesKey或者ASPNETCORE_HOSTINGSTARTUPASSEMBLIES指定多個程序集名稱可以使用英文分號(;)隔開程序集名稱。雖然是兩種形似指定,但是其實本質(zhì)是一樣的那就是設(shè)置配置key為hostingStartupAssemblie配置的值,下面我們會詳細講解。
通過在程序中設(shè)置環(huán)境變量的方式等同于Window系統(tǒng)中Set的方式設(shè)置環(huán)境變量,或Linux系統(tǒng)中export的方式設(shè)置環(huán)境變量,亦或是直接設(shè)置系統(tǒng)環(huán)境變量,效果都是一致的。指定完成啟動程序集之后,再次運行程序便可以看到HostingStartupInLib的Configure方法被調(diào)用到了。在這里我們可以看到如果是使用的環(huán)境變量的方式去指定啟動程序集的話,對現(xiàn)有代碼可以做到完全無入侵。
源碼探究
在上面我們簡單的介紹了HostingStartup的概念及基本的使用方式,基于這些我們產(chǎn)生了幾個疑問
- 首先是關(guān)于HostingStartup的基本工作方式是什么
- 其次是為什么HostingStartup在Web程序中不需要配置程序集信息就可以被調(diào)用到,而通過外部程序集引入HostingStartup需要手動指定程序集
- 最后是通過外部程序集引入HostingStartup的指定方式為何只能是UseSetting和環(huán)境變量的方式
- 基于以上幾個疑問,我們來探索一下HostingStartup的相關(guān)源碼,來揭開它的神秘面紗。首先廢話不多說直接找到源碼位置[點擊查看源碼👈]在GenericWebHostBuilder類中的ExecuteHostingStartups方法中,關(guān)于GenericWebHostBuilder類我們在上篇文章深入探究ASP.NET Core Startup初始化中主要就是分析這個類,因為這是構(gòu)建WebHost的默認類,而我們接下來要說的ExecuteHostingStartups方法也是承載在這個類中,直接貼代碼如下所示
private void ExecuteHostingStartups() { //通過配置_config和當前程序集名稱構(gòu)建WebHostOptions類 var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name); //如果PreventHostingStartup屬性為true則直接返回 //通過這個可以配置阻止啟動邏輯 if (webHostOptions.PreventHostingStartup) { return; } var exceptions = new List<Exception>(); //構(gòu)建HostingStartupWebHostBuilder _hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this); //GetFinalHostingStartupAssemblies獲取最終要執(zhí)行的程序集名稱 foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase)) { try { //通過程序集名稱加載程序集信息,因為使用了AssemblyName所以只需要使用程序集名稱即可 var assembly = Assembly.Load(new AssemblyName(assemblyName)); //獲取包含HostingStartupAttribute的程序集 foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>()) { //實例化HostingStartupAttribute的HostingStartupType屬性的對象實例 //即我們上面聲明的[assembly: HostingStartup(typeof(HostStartupWeb.HostingStartupInWeb))] var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType); //調(diào)用HostingStartup的Configure方法 hostingStartup.Configure(_hostingStartupWebHostBuilder); } } catch (Exception ex) { exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex)); } } if (exceptions.Count > 0) { _hostingStartupErrors = new AggregateException(exceptions); } }
通過上面的源碼我們就可以很清楚的了解到HostingStartup的基本工作方式。獲取的程序集中包含的HostingStartupAttribute,通過獲取HostingStartupAttribute的HostingStartupType屬性得到要執(zhí)行的IHostingStartup實例,最后執(zhí)行Configure方法,Configure方法需要傳遞IWebHostBuilder的實例,而HostingStartupWebHostBuilder正是實現(xiàn)了IWebHostBuilder接口。
我們了解到了HostStartup的工作方式,接下來我們來探究一下為什么HostingStartup在Web程序中不需要配置程序集信息就可以被調(diào)用到,而通過外部程序集引入HostingStartup需要手動指定程序集。通過上面的源碼我們可以得到一個信息那就是所有需要啟動的程序集信息都是來自WebHostOptions的GetFinalHostingStartupAssemblies方法,接下來我們就來查看一下GetFinalHostingStartupAssemblies方法的實現(xiàn)源碼[點擊查看源碼👈]
public IEnumerable<string> GetFinalHostingStartupAssemblies() { return HostingStartupAssemblies.Except(HostingStartupExcludeAssemblies, StringComparer.OrdinalIgnoreCase); }
從這里我們可以看出程序集信息來自于HostingStartupAssemblies屬性,而且還要排除掉HostingStartupExcludeAssemblies包含的程序集。我們找到他們初始化的相關(guān)邏輯大致如下
//承載啟動是需要調(diào)用的HostingStartup程序集 public IReadOnlyList<string> HostingStartupAssemblies { get; set; } //承載啟動時排除掉不不要執(zhí)行的程序集 public IReadOnlyList<string> HostingStartupExcludeAssemblies { get; set; } //是否阻止HostingStartup啟動執(zhí)行功能,如果設(shè)置為false則HostingStartup功能失效 //通過上面的ExecuteHostingStartups方法源碼可知 public bool PreventHostingStartup { get; set; } //應(yīng)用程序名稱 public string ApplicationName { get; set; } public WebHostOptions(IConfiguration configuration, string applicationNameFallback) { ApplicationName = configuration[WebHostDefaults.ApplicationKey] ?? applicationNameFallback; HostingStartupAssemblies = Split($"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}"); HostingStartupExcludeAssemblies = Split(configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]); PreventHostingStartup = WebHostUtilities.ParseBool(configuration, WebHostDefaults.PreventHostingStartupKey); } //分隔配置的程序集信息,分隔依據(jù)為";"分號,這也是我們上面說過配置多程序集的時候采用分號分隔的原因 private IReadOnlyList<string> Split(string value) { return value?.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>(); }
首先,通過HostingStartupAssemblies的初始化邏輯我們可以得出,默認會是有兩個數(shù)據(jù)來源,一個是當前的ApplicationName,另一個是通過HostingStartupAssembliesKey配置的程序集信息。這也解答了我們上面說過的為什么HostingStartup在Web程序中不需要配置程序集信息就可以被調(diào)用到,而通過外部程序集引入HostingStartup需要手動指定程序集。其次,我們可以了解到通過配置HostingStartupExcludeAssemblies信息排除你不想啟動的HostingStartup程序集,而且還可以通過配置PreventHostingStartup值來禁止使用HostingStartup的功能。
通過上面的代碼我們還了解到這三個屬性的來源的配置名稱都是來自WebHostDefaults這個常量類,接下來我們查看一下這三個屬性對應(yīng)的配置名稱
public static readonly string HostingStartupAssembliesKey = "hostingStartupAssemblies"; public static readonly string HostingStartupExcludeAssembliesKey = "hostingStartupExcludeAssemblies"; public static readonly string PreventHostingStartupKey = "preventHostingStartup";
也就是說,我們可以可以通過配置這三個名稱的配置,來完成HostingStartup相關(guān)的功能比如
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { //通過UseSetting的方式指定程序集的名稱 webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib"); //如果HostingStartup存在多個程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2 //webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib;HostStartupLib2"); //排除執(zhí)行HostStartupLib2程序集執(zhí)行HostingStartup邏輯 webBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, "HostStartupLib2"); //禁用HostingStartup功能 webBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true"); webBuilder.UseStartup<Startup>(); });
或通過環(huán)境變量的方式去操作
"environmentVariables": { "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib", //如果HostingStartup存在多個程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2 //"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib;HostStartupLib2" //排除執(zhí)行HostStartupLib2程序集執(zhí)行HostingStartup邏輯 "ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES":"HostStartupLib2", //禁用HostingStartup功能 "ASPNETCORE_PREVENTHOSTINGSTARTUP":"true" }
其實這兩種配置方式是完全等價的,為什么這么說呢?首先是在Configuration中獲取配置是忽略大小寫的,其實是使用ConfigureWebHostDefaults配置WebHost相關(guān)信息的時候會添加configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_")邏輯這樣的話獲取環(huán)境變量的時候可以忽略ASPNETCORE_前綴。
那么到目前為止,還有一個疑問尚未解決,那就是為何只能通過UseSetting和環(huán)境變量的方式去配置HostingStartup相關(guān)配置,解鈴還須系鈴人,我們在上面的ExecuteHostingStartups方法中看到了這個邏輯
//這里傳遞了一個_config var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
我們可以看到傳遞了配置Configuration的實例_config,我們到初始化_config地方有如下邏輯
var configBuilder = new ConfigurationBuilder() .AddInMemoryCollection(); if (!options.SuppressEnvironmentConfiguration) { //添加環(huán)境變量 configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_"); } //構(gòu)建了_config實例 private readonly IConfiguration _config = configBuilder.Build();
也就可以解釋為何我們可以通過環(huán)境變量去配置HostingStartup,然后我們再來看UseSetting方法的邏輯
public IWebHostBuilder UseSetting(string key, string value) { _config[key] = value; return this; }
原來UseSetting也是給_config實例設(shè)置值,所以無論通過UseSetting或環(huán)境環(huán)境變量的方式去配置,本質(zhì)都是在操作_config這個配置實例,到此為止所有謎團均以解開。
在SkyAPM中的使用
我們上面說了HostingStartup可以增強啟動時候的操作,可以通過對現(xiàn)有代碼無入侵的方式增強程序功能。而SkyAPM-dotnet也正是使用了這個功能,實現(xiàn)了無入侵啟動APM監(jiān)控。我們來回顧一下SkyAPM-dotnet的使用方式
首先是使用Nuget添加SkyAPM.Agent.AspNetCore程序集引用。
- 其次是在launchSettings.json文件中添加ASPNETCORE_HOSTINGSTARTUPASSEMBLIES:"SkyAPM.Agent.AspNetCore"環(huán)境變量配置(等同于set ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=SkyAPM.Agent.AspNetCore或export ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=SkyAPM.Agent.AspNetCore
- 的方式,本質(zhì)都是在配置環(huán)境變量)
- 最后通過SKYWALKING__SERVICENAME設(shè)置程序名稱
- 這里我們通過需要配置ASPNETCORE_HOSTINGSTARTUPASSEMBLIES名稱可以看出確實是使用了HostingStartup功能,而通過HostingStartup增強的操作入口肯定就在SkyAPM.Agent.AspNetCore程序集中,我們找到SkyAPM.Agent.AspNetCore程序集的源碼[點擊查看源碼👈]看到了SkyApmHostingStartup類實現(xiàn)如下
using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using SkyApm.Agent.AspNetCore; using SkyApm.AspNetCore.Diagnostics; [assembly: HostingStartup(typeof(SkyApmHostingStartup))] namespace SkyApm.Agent.AspNetCore { internal class SkyApmHostingStartup : IHostingStartup { public void Configure(IWebHostBuilder builder) { builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting())); } } }
通過這個我們可以看出確實如此,當然也是等同于我們通過UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SkyApm.Agent.AspNetCore")去配置,我們甚至可使用如下的方式去使用SkyAPM-dotnet
public void ConfigureServices(IServiceCollection services) { services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()) }
這些寫法其實是完全等價的,但是通過環(huán)境變量的方式配置HostingStartup啟動程序集的方式無疑是最優(yōu)雅的。所以我們在日常的學(xué)習(xí)開發(fā)中,最好還是通過這種方式去操作。
改造Zipkin使用
我們在之前的文章ASP.NET Core整合Zipkin鏈路跟蹤中曾演示過基于診斷日志DiagnosticSource改進Zipkin的集成方式,通過本篇文章講述的HostingStartup我們可以進步一改進Zipkin的集成方式,可以讓它使用起來和SkyAPM-dotnet類似的方式,我們基于之前的示例中的ZipkinExtensions程序集中添加一個ZipkinHostingStartup類,用于承載集成Zipkin的操作,代碼如下
using System; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; namespace ZipkinExtensions { public class ZipkinHostingStartup: IHostingStartup { public void Configure(IWebHostBuilder builder) { builder.ConfigureServices(services=> { services.AddZipkin(); services.AddSingleton<ITraceDiagnosticListener, HttpDiagnosticListener>(); }); builder.Configure(app=> { IHostApplicationLifetime lifetime = app.ApplicationServices.GetService<IHostApplicationLifetime>(); ILoggerFactory loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>(); IConfiguration configuration = app.ApplicationServices.GetService<IConfiguration>(); string serivceName = configuration.GetValue<string>("ServiceName"); string zipKinUrl = configuration.GetValue<string>("ASPNETCORE_ZIPKINADDRESS"); app.UseZipkin(lifetime, loggerFactory, serivceName, zipKinUrl); }); } } }
然后在每個項目的launchSettings.json文件中添加如下所示的配置即可,這樣的話就可以做到對現(xiàn)有業(yè)務(wù)代碼無任何入侵。
"environmentVariables": { "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "ZipkinExtensions", "ASPNETCORE_ZIPKINADDRESS": "http://localhost:9411/" }
總結(jié)
本文介紹了HostingStartup的基本概念,基礎(chǔ)使用以及對其源碼的分析和在SkyAPM-dotnet中的應(yīng)用,最后我們改造了Zipkin的集成方式。HostingStartup在一些集成APM或者鏈路跟蹤的類似場景還是非常實用的,或者如果我們有集成一些基礎(chǔ)組件或者三方的組件,但是我們的代碼中并不需要直接的使用這些組件中的類或者直接的代碼關(guān)系,均可以使用HostingStartup的方式去集成,為我們實現(xiàn)對現(xiàn)有代碼提供無入侵增強提供了強大的支持。關(guān)于HostingStartup我也是在看源碼中無意發(fā)現(xiàn)的,后來發(fā)現(xiàn)微軟ASP.NET Core官方文檔
Use hosting startup assemblies in ASP.NET Core一文中有講解,然后聯(lián)想到自己使用過的SkyAPM-dotnet正是使用了HostingStartup+診斷日志DiagnosticSource的方式實現(xiàn)了對代碼無入侵的方式進行監(jiān)控和鏈路跟蹤。于是決定深入研究一下,可謂收獲滿滿,便寫下這篇文章希望更多的人能夠了解使用這個功能。
到此這篇關(guān)于ASP.NET Core使用HostingStartup增強啟動操作的文章就介紹到這了,更多相關(guān)ASP.NET Core使用HostingStartup增強啟動操作內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何給ASP.NET Core Web發(fā)布包做減法詳解
在ASP.Net中可以使用打包與壓縮這兩種技術(shù)來提高Web應(yīng)用程序頁面加載的性能。下面這篇文章主要給大家介紹了關(guān)于如何給ASP.NET Core Web發(fā)布包做減法的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧2018-06-06ASP.NET Core 配置和使用環(huán)境變量的實現(xiàn)
這篇文章主要介紹了ASP.NET Core 配置和使用環(huán)境變量的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08EF使用Code First模式給實體類添加復(fù)合主鍵
這篇文章介紹了EF使用Code First模式給實體類添加復(fù)合主鍵的方法,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03ASP.net中實現(xiàn)基于UrlRewrite的防盜鏈功能
這篇文章主要介紹了ASP.net中如何實現(xiàn)基于UrlRewrite的防盜鏈,需要的朋友可以參考下2014-03-03Repeater的FooterTemplate中控件內(nèi)容設(shè)置方法
Repeater的FooterTemplate中控件內(nèi)容設(shè)置方法,需要的朋友可以參考下。2009-12-12解析WPF綁定層次結(jié)構(gòu)數(shù)據(jù)的應(yīng)用詳解
本文講述WPF中單層次數(shù)據(jù)和多層次數(shù)據(jù)的綁定方法,主要闡述數(shù)據(jù)綁定的顯示層面,其中涉及了ListBox和Treeview控件。并說明它們之間的差異2013-05-05