ASP.NET?Core在Task中使用IServiceProvider的問題解析
前言
問題的起因是在幫同事解決遇到的一個(gè)問題,他的本意是在EF Core中為了解決避免多個(gè)線程使用同一個(gè)DbContext實(shí)例的問題。但是由于對(duì)Microsoft.Extensions.DependencyInjection體系的深度不是很了解,結(jié)果遇到了新的問題,當(dāng)時(shí)整得我也有點(diǎn)蒙了,所以當(dāng)時(shí)也沒解決,而且當(dāng)時(shí)快下班了,就想著第二天再解決。在地鐵上,經(jīng)過我一系列的思維跳躍,終于想到了問題的原因,第二天也順利的解決了這個(gè)問題。雖然我前面說了EFCore,但是本質(zhì)和EFCore沒有關(guān)系,只是湊巧。解決了之后覺得這個(gè)問題是個(gè)易錯(cuò)題,覺得挺有意思的,便趁機(jī)記錄一下。
問題演示
接下來我們還原一下當(dāng)時(shí)的場(chǎng)景,以下代碼只是作為演示,無任何具體含義,只是為了讓操作顯得更清晰一下,接下來就貼一下當(dāng)時(shí)的場(chǎng)景代碼
[Route("api/[controller]/[action]")]
[ApiController]
public class InformationController : ControllerBase
{
private readonly LibraryContext _libraryContext;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<InformationController> _logger;
public InformationController(LibraryContext libraryContext,
IServiceProvider serviceProvider,
ILogger<InformationController> logger)
{
_libraryContext = libraryContext;
_serviceProvider = serviceProvider;
_logger = logger;
}
[HttpGet]
public string GetFirst()
{
var caseInfo = _libraryContext.Informations.Where(i => i.IsDelete == 0).FirstOrDefault();
//這里直接使用了Task方式
Task.Run(() => {
try
{
//Task里創(chuàng)建了新的IServiceScope
using var scope = _serviceProvider.CreateScope();
//通過IServiceScope創(chuàng)建具體實(shí)例
LibraryContext dbContext = scope.ServiceProvider.GetService<LibraryContext>();
var list = dbContext!.Informations.Where(i => i.IsDelete == 0).Take(100).ToList();
}
catch (Exception ex)
{
_logger.LogError(ex.Message, ex);
}
});
return caseInfo.Title;
}
}
再次強(qiáng)調(diào)一下,上述代碼純粹是為了讓演示更清晰,無任何業(yè)務(wù)含義,不喜勿噴。咱們首先看一下這段代碼表現(xiàn)出來的意思,就是在ASP.NET Core的項(xiàng)目里,在Task.Run里使用IServiceProvider去創(chuàng)建Scope的場(chǎng)景。如果對(duì)ASP.NET Core Controller生命周期和IServiceProvider不夠了解的話,會(huì)很容易遇到這個(gè)問題,且不知道是什么原因。上述這段代碼會(huì)偶現(xiàn)一個(gè)錯(cuò)誤
Cannot access a disposed object.
Object name: 'IServiceProvider'.
這里為什么說是偶現(xiàn)呢?因?yàn)闀?huì)不會(huì)出現(xiàn)異常完全取決于Task.Run里的代碼是在當(dāng)前請(qǐng)求輸出之前執(zhí)行完成還是之后完成。說到這里相信有一部分同學(xué)已經(jīng)猜到了代碼報(bào)錯(cuò)的原因了。問題的本質(zhì)很簡(jiǎn)單,是因?yàn)?code>IServiceProvider被釋放掉了。我們知道默認(rèn)情況下ASP.NET Core為每次請(qǐng)求處理會(huì)創(chuàng)建單獨(dú)的IServiceScope,這會(huì)關(guān)乎到聲明周期為Scope對(duì)象的聲明周期。所以如果Task.Run里的邏輯在請(qǐng)求輸出之前執(zhí)行完成,那么代碼運(yùn)行沒任何問題。如果是在請(qǐng)求完成之后完成再執(zhí)行CreateScope操作,那必然會(huì)報(bào)錯(cuò)。因?yàn)?code>Task.Run里的邏輯何時(shí)被執(zhí)行,這個(gè)是由系統(tǒng)CPU調(diào)度本身決定的,特別是CPU比較繁忙的時(shí)候,這種異常會(huì)變得更加頻繁。
這個(gè)問題不僅僅是在Task.Run這種場(chǎng)景里,類似的本質(zhì)就是在一個(gè)IServiceScope里創(chuàng)建一個(gè)新的子Scope作用域的時(shí)候,這個(gè)時(shí)候需要注意的是父級(jí)的IServiceProvider釋放問題,如果父級(jí)的IServiceProvider已經(jīng)被釋放了,那么基于這個(gè)Provider再去創(chuàng)建Scope則會(huì)出現(xiàn)異常。但是這個(gè)問題在結(jié)合Task或者多線程的時(shí)候,更容易出現(xiàn)問題。
解決問題
既然我們知道了它為何會(huì)出現(xiàn)異常,那么解決起來也就順理成章了。那就是保證當(dāng)前請(qǐng)求執(zhí)行完成之前,最好保證Task.Run里的邏輯也要執(zhí)行完成,所以我們上述的代碼會(huì)變成這樣
[HttpGet]
public async Task<string> GetFirst()
{
var caseInfo = _libraryContext.Informations.Where(i => i.IsDelete == 0).FirstOrDefault();
//這里使用了await Task方式
await Task.Run(() => {
try
{
//Task里創(chuàng)建了新的IServiceScope
using var scope = _serviceProvider.CreateScope();
//通過IServiceScope創(chuàng)建具體實(shí)例
LibraryContext dbContext = scope.ServiceProvider.GetService<LibraryContext>();
var list = dbContext!.Informations.Where(i => i.IsDelete == 0).Take(100).ToList();
}
catch (Exception ex)
{
_logger.LogError(ex.Message, ex);
}
});
return caseInfo.Title;
}試一下,發(fā)現(xiàn)確實(shí)能解決問題,因?yàn)榈却齌ask完成能保證Task里的邏輯能在請(qǐng)求執(zhí)行完成之前完成。但是,很多時(shí)候我們并不需要等待Task執(zhí)行完成,因?yàn)槲覀兙褪窍M诤笈_(tái)線程去執(zhí)行這些操作,而不需要阻塞執(zhí)行。
上面我們提到了本質(zhì)是解決在IServiceScope創(chuàng)建子Scope時(shí)遇到的問題,因?yàn)檫@里注入進(jìn)來的IServiceProvider本身是Scope的,只在當(dāng)前請(qǐng)求內(nèi)有效,所以基于IServiceProvider去創(chuàng)建IServiceScope要考慮到當(dāng)前IServiceProvider是否釋放。那么我們就得打破這個(gè)枷鎖,我們要想辦法在根容器中去創(chuàng)建新的IServiceScope。這一點(diǎn)我大微軟自然是考慮到了,在Microsoft.Extensions.DependencyInjection體系中提供了IServiceScopeFactory這個(gè)根容器的作用域,基于根容器創(chuàng)建的IServiceScope可以得到平行與當(dāng)前請(qǐng)求作用域的獨(dú)立的作用域,而不受當(dāng)前請(qǐng)求的影響。改造上面的代碼用以下形式
[Route("api/[controller]/[action]")]
[ApiController]
public class InformationController : ControllerBase
{
private readonly LibraryContext _libraryContext;
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<InformationController> _logger;
public InformationController(LibraryContext libraryContext,
IServiceScopeFactory scopeFactory,
ILogger<InformationController> logger)
{
_libraryContext = libraryContext;
_scopeFactory = scopeFactory;
_logger = logger;
}
[HttpGet]
public string GetFirst()
{
var caseInfo = _libraryContext.Informations.Where(i => i.IsDelete == 0).FirstOrDefault();
//這里直接使用了Task方式
Task.Run(() => {
try
{
//Task里創(chuàng)建了新的IServiceScope
using var scope = _scopeFactory.CreateScope();
//通過IServiceScope創(chuàng)建具體實(shí)例
LibraryContext dbContext = scope.ServiceProvider.GetService<LibraryContext>();
var list = dbContext!.Informations.Where(i => i.IsDelete == 0).Take(100).ToList();
}
catch (Exception ex)
{
_logger.LogError(ex.Message, ex);
}
});
return caseInfo.Title;
}
}如果你是調(diào)試起來的話你可以看到IServiceScopeFactory的具體實(shí)例是Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope類型的,它里面包含了一個(gè)IsRootScope屬性,通過這個(gè)屬性我們可以知道當(dāng)前容器作用域是否是根容器作用域。當(dāng)使用IServiceProvider實(shí)例的時(shí)候IsRootScope為false,當(dāng)使用IServiceScopeFactory實(shí)例的時(shí)候IsRootScope為true。使用CreateScope創(chuàng)建IServiceScope實(shí)例的時(shí)候,注意用完了需要釋放,否則可能會(huì)導(dǎo)致Transient和Scope類型的實(shí)例得不到釋放。在之前的文章咱們?cè)岬竭^Transient和Scope類型的實(shí)例都是在當(dāng)前容器作用域釋放的時(shí)候釋放的,這個(gè)需要注意一下。
問題探究
上面我們了解到了在每次請(qǐng)求的時(shí)候使用IServiceProvider和使用IServiceScopeFactory的時(shí)候他們作用域的實(shí)例來源是不一樣的。IServiceScopeFactory來自根容器,IServiceProvider則是來自當(dāng)前請(qǐng)求的Scope。順著這個(gè)思路我們可以看一下他們兩個(gè)究竟是如何的不相同。這個(gè)問題還得從構(gòu)建Controller實(shí)例的時(shí)候,注入到Controller中的實(shí)例作用域的問題。
請(qǐng)求中的IServiceProvider
在之前的文章<ASP.NET Core Controller與IOC的羈絆>我們知道,Controller是每次請(qǐng)求都會(huì)創(chuàng)建新的實(shí)例,我們?cè)俅文贸鰜磉@段核心的代碼來看一下,在DefaultControllerActivator類的Create方法中[點(diǎn)擊查看源碼??]
internal class DefaultControllerActivator : IControllerActivator
{
private readonly ITypeActivatorCache _typeActivatorCache;
public DefaultControllerActivator(ITypeActivatorCache typeActivatorCache)
{
_typeActivatorCache = typeActivatorCache;
}
public object Create(ControllerContext controllerContext)
{
//省略一系列判斷代碼
var serviceProvider = controllerContext.HttpContext.RequestServices;
//這里傳遞的IServiceProvider本質(zhì)就是來自HttpContext.RequestServices
return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType());
}
}通過這個(gè)方法我們可以看到創(chuàng)建Controller實(shí)例時(shí),如果存在構(gòu)造依賴,本質(zhì)則是通過HttpContext.RequestServices實(shí)例創(chuàng)建出來的,而它本身就是IServiceProvider的實(shí)例,ITypeActivatorCache實(shí)例中則存在真正創(chuàng)建Controller實(shí)例的邏輯,具體可以查看TypeActivatorCache類的實(shí)現(xiàn)[點(diǎn)擊查看源碼??]
internal class TypeActivatorCache : ITypeActivatorCache
{
private readonly Func<Type, ObjectFactory> _createFactory =
(type) => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes);
private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache =
new ConcurrentDictionary<Type, ObjectFactory>();
public TInstance CreateInstance<TInstance>(
IServiceProvider serviceProvider,
Type implementationType)
{
//省略一系列判斷代碼
var createFactory = _typeActivatorCache.GetOrAdd(implementationType, _createFactory);
//創(chuàng)建Controller的時(shí)候,需要的依賴實(shí)例都是來自IServiceProvider
return (TInstance)createFactory(serviceProvider, arguments: null);
}
}
其實(shí)在這里我們就可以得到一個(gè)結(jié)論,我們?cè)诋?dāng)前請(qǐng)求默認(rèn)通過構(gòu)造注入的IServiceProvider的實(shí)例其實(shí)就是HttpContext.RequestServices,也就是針對(duì)當(dāng)前請(qǐng)求的作用域有效,同樣的是來自當(dāng)前作用域的Scope周期的對(duì)象實(shí)例也是在當(dāng)前請(qǐng)求結(jié)束就會(huì)釋放。驗(yàn)證這個(gè)很簡(jiǎn)單可以寫個(gè)demo來演示一下
[Route("api/[controller]/[action]")]
[ApiController]
public class InformationController : ControllerBase
{
private readonly IServiceProvider _serviceProvider;
public InformationController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[HttpGet]
public bool[] JudgeScope([FromServices]IServiceProvider scopeProvider)
{
//比較構(gòu)造注入的和在HttpContext獲取的
bool isEqualOne = _serviceProvider == HttpContext.RequestServices;
//比較通過Action綁定的和在HttpContext獲取的
bool isEqualTwo = scopeProvider == HttpContext.RequestServices;
return new[] { isEqualOne, isEqualTwo };
}
}毫無疑問,默認(rèn)情況下isEqualOne和isEqualTwo的結(jié)構(gòu)都是true,這也驗(yàn)證了我們上面的結(jié)論。因此在當(dāng)前請(qǐng)求默認(rèn)注入IServiceProvider實(shí)例的時(shí)候,都是來自HttpContext.RequestServices的實(shí)例。
請(qǐng)求中的IServiceProvider和IServiceScopeFactory
上面我們看到了在當(dāng)前請(qǐng)求中獲取IServiceProvider實(shí)例本身就是Scope的,而且在當(dāng)前請(qǐng)求中通過各種注入方式獲取到的實(shí)例都是相同的。那么接下來我們就可以繼續(xù)跟蹤,本質(zhì)的HttpContext.RequestServices的IServiceProvider到底來自什么地方呢?我們找到HttpContext默認(rèn)的實(shí)現(xiàn)類DefaultHttpContext中關(guān)于RequestServices屬性的定義[點(diǎn)擊查看源碼??]
//接受
public IServiceScopeFactory ServiceScopeFactory { get; set; } = default!;
//數(shù)據(jù)來自RequestServicesFeature
private static readonly Func<DefaultHttpContext, IServiceProvidersFeature> _newServiceProvidersFeature = context => new RequestServicesFeature(context, context.ServiceScopeFactory);
//緩存來自_newServiceProvidersFeature
private IServiceProvidersFeature ServiceProvidersFeature =>
_features.Fetch(ref _features.Cache.ServiceProviders, this, _newServiceProvidersFeature)!;
//數(shù)據(jù)來自ServiceProvidersFeature的RequestServices
public override IServiceProvider RequestServices
{
get { return ServiceProvidersFeature.RequestServices; }
set { ServiceProvidersFeature.RequestServices = value; }
}通過上面的源碼我們可以看到HttpContext.RequestServices的數(shù)據(jù)最終來自RequestServicesFeature類的RequestServices屬性,我們可以直接找到RequestServicesFeature類的定義[點(diǎn)擊查看源碼??]
public class RequestServicesFeature : IServiceProvidersFeature, IDisposable, IAsyncDisposable
{
private readonly IServiceScopeFactory? _scopeFactory;
private IServiceProvider? _requestServices;
private IServiceScope? _scope;
private bool _requestServicesSet;
private readonly HttpContext _context;
public RequestServicesFeature(HttpContext context, IServiceScopeFactory? scopeFactory)
{
_context = context;
_scopeFactory = scopeFactory;
}
public IServiceProvider RequestServices
{
get
{
if (!_requestServicesSet && _scopeFactory != null)
{
//釋放掉之前沒釋放掉的RequestServicesFeature實(shí)例
_context.Response.RegisterForDisposeAsync(this);
//通過IServiceScopeFactory創(chuàng)建Scope
_scope = _scopeFactory.CreateScope();
//RequestServices來自IServiceScopeFactory的CreateScope實(shí)例
_requestServices = _scope.ServiceProvider;
//填充已經(jīng)設(shè)置了RequestServices的標(biāo)識(shí)
_requestServicesSet = true;
}
return _requestServices!;
}
set
{
_requestServices = value;
_requestServicesSet = true;
}
}
//釋放的真實(shí)邏輯
public ValueTask DisposeAsync()
{
switch (_scope)
{
case IAsyncDisposable asyncDisposable:
var vt = asyncDisposable.DisposeAsync();
if (!vt.IsCompletedSuccessfully)
{
return Awaited(this, vt);
}
vt.GetAwaiter().GetResult();
break;
case IDisposable disposable:
disposable.Dispose();
break;
}
//釋放時(shí)重置相關(guān)屬性
_scope = null;
_requestServices = null;
return default;
static async ValueTask Awaited(RequestServicesFeature servicesFeature, ValueTask vt)
{
await vt;
servicesFeature._scope = null;
servicesFeature._requestServices = null;
}
}
//IDisposable的Dispose的方法,通過using可隱式調(diào)用
public void Dispose()
{
DisposeAsync().AsTask().GetAwaiter().GetResult();
}
}通過上面的兩段源碼,我們得到了許多關(guān)于IServiceProvider和IServiceScopeFactory的相關(guān)信息。
- DefaultHttpContext的RequestServices值來自于RequestServicesFeature實(shí)例的RequestServices屬性
- RequestServicesFeature的RequestServices屬性的值通過IServiceScopeFactory通過CreateScope創(chuàng)建的
- 構(gòu)建RequestServicesFeature的IServiceScopeFactory值來自于DefaultHttpContext的ServiceScopeFactory屬性
那么接下來我們直接可以找到DefaultHttpContext的ServiceScopeFactory屬性是誰給它賦的值,我們找到創(chuàng)建HttpContext的地方,在DefaultHttpContextFactory的Create方法里[點(diǎn)擊查看源碼??]
public class DefaultHttpContextFactory : IHttpContextFactory
{
private readonly IHttpContextAccessor? _httpContextAccessor;
private readonly FormOptions _formOptions;
private readonly IServiceScopeFactory _serviceScopeFactory;
public DefaultHttpContextFactory(IServiceProvider serviceProvider)
{
_httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
_formOptions = serviceProvider.GetRequiredService<IOptions<FormOptions>>().Value;
//通過IServiceProvider的GetRequiredService直接獲取IServiceScopeFactory
_serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
}
//創(chuàng)建HttpContext實(shí)例的方法
public HttpContext Create(IFeatureCollection featureCollection)
{
if (featureCollection is null)
{
throw new ArgumentNullException(nameof(featureCollection));
}
var httpContext = new DefaultHttpContext(featureCollection);
Initialize(httpContext);
return httpContext;
}
private DefaultHttpContext Initialize(DefaultHttpContext httpContext)
{
//IHttpContextAccessor也是在這里賦的值
if (_httpContextAccessor != null)
{
_httpContextAccessor.HttpContext = httpContext;
}
httpContext.FormOptions = _formOptions;
//DefaultHttpContext的ServiceScopeFactory屬性值來自注入的IServiceProvider
httpContext.ServiceScopeFactory = _serviceScopeFactory;
return httpContext;
}
}這里我們可以看到IServiceScopeFactory的實(shí)例來自于通過DefaultHttpContextFactory注入的IServiceProvider實(shí)例,這里獲取IServiceScopeFactory的地方并沒有CreateScope,所以這里的IServiceScopeFactory和IServiceProvider中的實(shí)例都是來自根容器。這個(gè)我們還可以通過注冊(cè)DefaultHttpContextFactory地方看到
[點(diǎn)擊查看源碼??]
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
通過這里可以看到DefaultHttpContextFactory注冊(cè)的是單例模式,注冊(cè)它的地方則是在IHostBuilder的ConfigureServices方法里。關(guān)于每次請(qǐng)求的創(chuàng)建流程,不是本文的重點(diǎn),但是為了讓大家對(duì)本文講解的IServiceScopeFactory和IServiceProvider來源更清楚,咱們可以大致的描述一下
GenericWebHostService類實(shí)現(xiàn)自IHostedService,在StartAsync方法中啟動(dòng)了IServer實(shí)例,默認(rèn)則是啟動(dòng)的Kestrel。IServer啟動(dòng)的方法StartAsync中會(huì)傳遞HostingApplication實(shí)例,構(gòu)建HostingApplication實(shí)例的時(shí)候則會(huì)依賴IHttpContextFactory實(shí)例,而IHttpContextFactory實(shí)例則是在構(gòu)建GenericWebHostService服務(wù)的時(shí)候注入進(jìn)來的。- 當(dāng)每次請(qǐng)求ASP.NET Core服務(wù)的時(shí)候會(huì)調(diào)用
HostingApplication的CreateContext方法,該方法中則會(huì)創(chuàng)建HttpContext實(shí)例,每次請(qǐng)求結(jié)束后則調(diào)用該類的DisposeContext釋放HttpContext實(shí)例。
說了這么多其實(shí)就是為了方便讓大家得到一個(gè)關(guān)系,即在每次請(qǐng)求中獲取的IServiceProvider實(shí)例來自HttpContext.RequestServices實(shí)例,HttpContext.RequestServices實(shí)例來自IServiceScopeFactory來自CreateScope方法創(chuàng)建的實(shí)例,而IServiceScopeFactory實(shí)例則是來自根容器,且DefaultHttpContextFactory的生命周期則和當(dāng)前ASP.NET Core保持一致。
后續(xù)插曲
就在解決這個(gè)問題后不久,有一次不經(jīng)意間翻閱微軟的官方文檔,發(fā)現(xiàn)官方文檔有提到相關(guān)的問題,而且也是結(jié)合efcore來講的。標(biāo)題是《Do not capture services injected into the controllers on background threads》翻譯成中文大概就是不要在后臺(tái)線程上捕獲注入控制器的服務(wù),說的正是這個(gè)問題,微軟給我們的建議是
- 注入一個(gè)IServiceScopeFactory以便在后臺(tái)工作項(xiàng)中創(chuàng)建一個(gè)范圍。
- IServiceScopeFactory是一個(gè)單例對(duì)象。
- 在后臺(tái)線程中創(chuàng)建一個(gè)新的依賴注入范圍。
- 不引用控制器中的任何東西。
- 不從傳入請(qǐng)求中捕獲DbContext。
得到的結(jié)論和我們?cè)诒疚拿枋龅幕旧鲜遣畈欢嗟?,而且微軟也很貼心的給我們提供了相關(guān)示例
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}原來還是自己的坑自己最了解,也不得不說微軟現(xiàn)在的文檔確實(shí)挺詳細(xì)的,同時(shí)也提醒我們有空還是得多翻一翻文檔避免踩坑。
總結(jié)
本文主要是通過幫助同事解決問題而得到的靈感,覺得挺有意思的,希望能幫助更多的人了解這個(gè)問題,且能避免這個(gè)問題。我們應(yīng)該深刻理解ASP.NET Core處理每次請(qǐng)求則都會(huì)創(chuàng)建一個(gè)Scope,這會(huì)影響當(dāng)前請(qǐng)求獲取的IServiceProvider實(shí)例,和通過IServiceProvider創(chuàng)建的生命周期為Scope的實(shí)例。如果把握不住,則可以理解為當(dāng)前請(qǐng)求直接注入的服務(wù),和當(dāng)前服務(wù)直接注入的IServiceProvider實(shí)例。如果想獲取根容器的實(shí)例則可以通過獲取IServiceScopeFactory實(shí)例獲取,最后請(qǐng)注意IServiceScope的釋放問題。
曾幾何時(shí),特別喜歡去解決遇到的問題,特別喜歡那種解決問題沉浸其中的過程。解決了問題,了解到為什么會(huì)讓自己感覺很通透,也更深刻,不經(jīng)意間的也擴(kuò)展了自己的認(rèn)知邊界。這個(gè)過程得到的經(jīng)驗(yàn)是一種通識(shí),是一種意識(shí)。而思維和意識(shí)則是我們適應(yīng)這個(gè)不斷在變化時(shí)代的底層邏輯。
到此這篇關(guān)于解決ASP.NET Core在Task中使用IServiceProvider的問題的文章就介紹到這了,更多相關(guān)ASP.NET Core使用IServiceProvider內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MVC4 基礎(chǔ) 枚舉生成 DropDownList 實(shí)用技巧
本篇文章小編為大家介紹,MVC4 基礎(chǔ) 枚舉生成 DropDownList 實(shí)用技巧。需要的朋友參考下2013-04-04
Asp.net core Web Api配置swagger中文的實(shí)現(xiàn)
swagger是一個(gè)api文檔自動(dòng)生動(dòng)工具,還集成了在線調(diào)試. 可以為項(xiàng)目自動(dòng)生成接口文檔, 非常的方便快捷,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
基于.NET BitmapImage 內(nèi)存釋放問題的解決方法詳解
本篇文章是對(duì).NET BitmapImage 內(nèi)存釋放問題的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
log4net創(chuàng)建系統(tǒng)日志的詳細(xì)步驟
log4net是.Net下一個(gè)非常優(yōu)秀的開源日志記錄組件。log4net記錄日志的功能非常強(qiáng)大。它可以將日志分不同的等級(jí),以不同的格式,輸出到不同的媒介。本文主要是簡(jiǎn)單的介紹如何在Visual Studio2010(Asp.Net Mvc3.0)中使用log4net快速創(chuàng)建系統(tǒng)日志,如何擴(kuò)展以輸出自定義字段2013-11-11
ASP.NET實(shí)現(xiàn)圖片以二進(jìn)制的形式存入數(shù)據(jù)庫
這篇文章主要介紹了ASP.NET實(shí)現(xiàn)圖片以二進(jìn)制的形式存入數(shù)據(jù)庫,有一定的學(xué)習(xí)借鑒價(jià)值,需要的朋友可以參考下2014-08-08
Silverlight融合ajax實(shí)現(xiàn)前后臺(tái)數(shù)據(jù)交互
兩年前Silverlight 還未起名,故事發(fā)生在WPF/E 的年代里。07年8月在中軟實(shí)習(xí)時(shí),我承擔(dān)起了在. Net 中嵌入WPF/E 的任務(wù),目的是增強(qiáng)用戶體驗(yàn)。2009-05-05
高效的使用 Response.Redirect解決一些不必要的問題
這篇文章主要介紹了如何高效的使用 Response.Redirect解決一些不必要的問題,需要的朋友可以參考下2014-05-05

