解决ASP.NET Core在Task中使用IServiceProvider的问题

前言

    问题的起因是在帮同事解决遇到的一个问题,他的本意是在EF Core中为了解决避免多个线程使用同一个DbContext实例的问题。但是由于对Microsoft.Extensions.DependencyInjection体系的深度不是很了解,结果遇到了新的问题,当时整得我也有点蒙了,所以当时也没解决,而且当时快下班了,就想着第二天再解决。在地铁上,经过我一系列的思维跳跃,终于想到了问题的原因,第二天也顺利的解决了这个问题。虽然我前面说了EFCore,但是本质和EFCore没有关系,只是凑巧。解决了之后觉得这个问题是个易错题,觉得挺有意思的,便趁机记录一下。

问题演示

接下来我们还原一下当时的场景,以下代码只是作为演示,无任何具体含义,只是为了让操作显得更清晰一下,接下来就贴一下当时的场景代码

[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里创建了新的IServiceScopeusing var scope = _serviceProvider.CreateScope();//通过IServiceScope创建具体实例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;}
}

再次强调一下,上述代码纯粹是为了让演示更清晰,无任何业务含义,不喜勿喷。咱们首先看一下这段代码表现出来的意思,就是在ASP.NET Core的项目里,在Task.Run里使用IServiceProvider去创建Scope的场景。如果对ASP.NET Core Controller生命周期和IServiceProvider不够了解的话,会很容易遇到这个问题,且不知道是什么原因。上述这段代码会偶现一个错误

Cannot access a disposed object.
Object name: 'IServiceProvider'.

这里为什么说是偶现呢?因为会不会出现异常完全取决于Task.Run里的代码是在当前请求输出之前执行完成还是之后完成。说到这里相信有一部分同学已经猜到了代码报错的原因了。问题的本质很简单,是因为IServiceProvider被释放掉了。我们知道默认情况下ASP.NET Core为每次请求处理会创建单独的IServiceScope,这会关乎到声明周期为Scope对象的声明周期。所以如果Task.Run里的逻辑在请求输出之前执行完成,那么代码运行没任何问题。如果是在请求完成之后完成再执行CreateScope操作,那必然会报错。因为Task.Run里的逻辑何时被执行,这个是由系统CPU调度本身决定的,特别是CPU比较繁忙的时候,这种异常会变得更加频繁。

这个问题不仅仅是在Task.Run这种场景里,类似的本质就是在一个IServiceScope里创建一个新的子Scope作用域的时候,这个时候需要注意的是父级的IServiceProvider释放问题,如果父级的IServiceProvider已经被释放了,那么基于这个Provider再去创建Scope则会出现异常。但是这个问题在结合Task或者多线程的时候,更容易出现问题。

解决问题

既然我们知道了它为何会出现异常,那么解决起来也就顺理成章了。那就是保证当前请求执行完成之前,最好保证Task.Run里的逻辑也要执行完成,所以我们上述的代码会变成这样

[HttpGet]
public async Task<string> GetFirst()
{var caseInfo = _libraryContext.Informations.Where(i => i.IsDelete == 0).FirstOrDefault();//这里使用了await Task方式await Task.Run(() => {try{//Task里创建了新的IServiceScopeusing var scope = _serviceProvider.CreateScope();//通过IServiceScope创建具体实例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;
}

试一下,发现确实能解决问题,因为等待Task完成能保证Task里的逻辑能在请求执行完成之前完成。但是,很多时候我们并不需要等待Task执行完成,因为我们就是希望它在后台线程去执行这些操作,而不需要阻塞执行。
上面我们提到了本质是解决在IServiceScope创建子Scope时遇到的问题,因为这里注入进来的IServiceProvider本身是Scope的,只在当前请求内有效,所以基于IServiceProvider去创建IServiceScope要考虑到当前IServiceProvider是否释放。那么我们就得打破这个枷锁,我们要想办法在根容器中去创建新的IServiceScope。这一点我大微软自然是考虑到了,在Microsoft.Extensions.DependencyInjection体系中提供了IServiceScopeFactory这个根容器的作用域,基于根容器创建的IServiceScope可以得到平行与当前请求作用域的独立的作用域,而不受当前请求的影响。改造上面的代码用以下形式

[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里创建了新的IServiceScopeusing var scope = _scopeFactory.CreateScope();//通过IServiceScope创建具体实例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;}
}

如果你是调试起来的话你可以看到IServiceScopeFactory的具体实例是Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope类型的,它里面包含了一个IsRootScope属性,通过这个属性我们可以知道当前容器作用域是否是根容器作用域。当使用IServiceProvider实例的时候IsRootScopefalse,当使用IServiceScopeFactory实例的时候IsRootScopetrue。使用CreateScope创建IServiceScope实例的时候,注意用完了需要释放,否则可能会导致TransientScope类型的实例得不到释放。在之前的文章咱们曾提到过TransientScope类型的实例都是在当前容器作用域释放的时候释放的,这个需要注意一下。

问题探究

上面我们了解到了在每次请求的时候使用IServiceProvider和使用IServiceScopeFactory的时候他们作用域的实例来源是不一样的。IServiceScopeFactory来自根容器,IServiceProvider则是来自当前请求的Scope。顺着这个思路我们可以看一下他们两个究竟是如何的不相同。这个问题还得从构建Controller实例的时候,注入到Controller中的实例作用域的问题。

请求中的IServiceProvider

在之前的文章<ASP.NET Core Controller与IOC的羁绊>[1]我们知道,Controller是每次请求都会创建新的实例,我们再次拿出来这段核心的代码来看一下,在DefaultControllerActivator类的Create方法中[点击查看源码👈[2]]

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本质就是来自HttpContext.RequestServicesreturn _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType());}
}

通过这个方法我们可以看到创建Controller实例时,如果存在构造依赖,本质则是通过HttpContext.RequestServices实例创建出来的,而它本身就是IServiceProvider的实例,ITypeActivatorCache实例中则存在真正创建Controller实例的逻辑,具体可以查看TypeActivatorCache类的实现[点击查看源码👈[3]]

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);//创建Controller的时候,需要的依赖实例都是来自IServiceProviderreturn (TInstance)createFactory(serviceProvider, arguments: null);}
}

其实在这里我们就可以得到一个结论,我们在当前请求默认通过构造注入的IServiceProvider的实例其实就是HttpContext.RequestServices,也就是针对当前请求的作用域有效,同样的是来自当前作用域的Scope周期的对象实例也是在当前请求结束就会释放。验证这个很简单可以写个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){//比较构造注入的和在HttpContext获取的bool isEqualOne = _serviceProvider == HttpContext.RequestServices;//比较通过Action绑定的和在HttpContext获取的bool isEqualTwo = scopeProvider == HttpContext.RequestServices;return new[] { isEqualOne, isEqualTwo };}
}

毫无疑问,默认情况下isEqualOne和isEqualTwo的结构都是true,这也验证了我们上面的结论。因此在当前请求默认注入IServiceProvider实例的时候,都是来自HttpContext.RequestServices的实例。

请求中的IServiceProvider和IServiceScopeFactory

上面我们看到了在当前请求中获取IServiceProvider实例本身就是Scope的,而且在当前请求中通过各种注入方式获取到的实例都是相同的。那么接下来我们就可以继续跟踪,本质的HttpContext.RequestServices的IServiceProvider到底来自什么地方呢?我们找到HttpContext默认的实现类DefaultHttpContext中关于RequestServices属性的定义[点击查看源码👈[4]]

//接受
public IServiceScopeFactory ServiceScopeFactory { get; set; } = default!;
//数据来自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)!;
//数据来自ServiceProvidersFeature的RequestServices
public override IServiceProvider RequestServices
{get { return ServiceProvidersFeature.RequestServices; }set { ServiceProvidersFeature.RequestServices = value; }
}

通过上面的源码我们可以看到HttpContext.RequestServices的数据最终来自RequestServicesFeature类的RequestServices属性,我们可以直接找到RequestServicesFeature类的定义[点击查看源码👈[5]]

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实例_context.Response.RegisterForDisposeAsync(this);//通过IServiceScopeFactory创建Scope_scope = _scopeFactory.CreateScope();//RequestServices来自IServiceScopeFactory的CreateScope实例_requestServices = _scope.ServiceProvider;//填充已经设置了RequestServices的标识_requestServicesSet = true;}return _requestServices!;}set{_requestServices = value;_requestServicesSet = true;}}//释放的真实逻辑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;}//释放时重置相关属性_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可隐式调用public void Dispose(){DisposeAsync().AsTask().GetAwaiter().GetResult();}
}

通过上面的两段源码,我们得到了许多关于IServiceProvider和IServiceScopeFactory的相关信息。

  • • DefaultHttpContext的RequestServices值来自于RequestServicesFeature实例的RequestServices属性

  • • RequestServicesFeature的RequestServices属性的值通过IServiceScopeFactory通过CreateScope创建的

  • • 构建RequestServicesFeature的IServiceScopeFactory值来自于DefaultHttpContext的ServiceScopeFactory属性

那么接下来我们直接可以找到DefaultHttpContext的ServiceScopeFactory属性是谁给它赋的值,我们找到创建HttpContext的地方,在DefaultHttpContextFactory的Create方法里[点击查看源码👈[6]]

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>();}//创建HttpContext实例的方法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属性值来自注入的IServiceProviderhttpContext.ServiceScopeFactory = _serviceScopeFactory;return httpContext;}
}

这里我们可以看到IServiceScopeFactory的实例来自于通过DefaultHttpContextFactory注入的IServiceProvider实例,这里获取IServiceScopeFactory的地方并没有CreateScope,所以这里的IServiceScopeFactoryIServiceProvider中的实例都是来自根容器。这个我们还可以通过注册DefaultHttpContextFactory地方看到 [点击查看源码👈[7]]

services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();

通过这里可以看到DefaultHttpContextFactory注册的是单例模式,注册它的地方则是在IHostBuilderConfigureServices方法里。关于每次请求的创建流程,不是本文的重点,但是为了让大家对本文讲解的IServiceScopeFactoryIServiceProvider来源更清楚,咱们可以大致的描述一下

  • • GenericWebHostService类实现自IHostedService,在StartAsync方法中启动了IServer实例,默认则是启动的Kestrel。

  • • IServer启动的方法StartAsync中会传递HostingApplication实例,构建HostingApplication实例的时候则会依赖IHttpContextFactory实例,而IHttpContextFactory实例则是在构建GenericWebHostService服务的时候注入进来的。

  • • 当每次请求ASP.NET Core服务的时候会调用HostingApplicationCreateContext方法,该方法中则会创建HttpContext实例,每次请求结束后则调用该类的DisposeContext释放HttpContext实例。

说了这么多其实就是为了方便让大家得到一个关系,即在每次请求中获取的IServiceProvider实例来自HttpContext.RequestServices实例,HttpContext.RequestServices实例来自IServiceScopeFactory来自CreateScope方法创建的实例,而IServiceScopeFactory实例则是来自根容器,且DefaultHttpContextFactory的生命周期则和当前ASP.NET Core保持一致。

后续插曲

就在解决这个问题后不久,有一次不经意间翻阅微软的官方文档,发现官方文档有提到相关的问题,而且也是结合efcore来讲的。标题是《Do not capture services injected into the controllers on background threads》[8]翻译成中文大概就是不要在后台线程上捕获注入控制器的服务,说的正是这个问题,微软给我们的建议是

  • • 注入一个IServiceScopeFactory以便在后台工作项中创建一个范围。

  • • IServiceScopeFactory是一个单例对象。

  • • 在后台线程中创建一个新的依赖注入范围。

  • • 不引用控制器中的任何东西。

  • • 不从传入请求中捕获DbContext。

得到的结论和我们在本文描述的基本上是差不多的,而且微软也很贴心的给我们提供了相关示例

[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();
}

原来还是自己的坑自己最了解,也不得不说微软现在的文档确实挺详细的,同时也提醒我们有空还是得多翻一翻文档避免踩坑。

总结

    本文主要是通过帮助同事解决问题而得到的灵感,觉得挺有意思的,希望能帮助更多的人了解这个问题,且能避免这个问题。我们应该深刻理解ASP.NET Core处理每次请求则都会创建一个Scope,这会影响当前请求获取的IServiceProvider实例,和通过IServiceProvider创建的生命周期为Scope的实例。如果把握不住,则可以理解为当前请求直接注入的服务,和当前服务直接注入的IServiceProvider实例。如果想获取根容器的实例则可以通过获取IServiceScopeFactory实例获取,最后请注意IServiceScope的释放问题。     曾几何时,特别喜欢去解决遇到的问题,特别喜欢那种解决问题沉浸其中的过程。解决了问题,了解到为什么会让自己感觉很通透,也更深刻,不经意间的也扩展了自己的认知边界。这个过程得到的经验是一种通识,是一种意识。而思维和意识则是我们适应这个不断在变化时代的底层逻辑。

引用链接

[1] <ASP.NET Core Controller与IOC的羁绊>: https://www.cnblogs.com/wucy/p/14222973.html
[2] 点击查看源码👈: https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Mvc/Mvc.Core/src/Controllers/DefaultControllerActivator.cs#L56
[3] 点击查看源码👈: https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Mvc/Mvc.Core/src/Infrastructure/TypeActivatorCache.cs#L38
[4] 点击查看源码👈: https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Http/Http/src/DefaultHttpContext.cs#L176
[5] 点击查看源码👈: https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Http/Http/src/Features/RequestServicesFeature.cs#L33
[6] 点击查看源码👈: https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Hosting/Hosting/src/Http/DefaultHttpContextFactory.cs#L45
[7] 点击查看源码👈: https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs#L93
[8] 《Do not capture services injected into the controllers on background threads》: https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-6.0#do-not-capture-services-injected-into-the-controllers-on-background-threads

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283844.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是SRE?一文详解SRE运维体系

在任何有一定规模的企业内部&#xff0c;一旦推行起来整个SRE的运维模式&#xff0c;那么对于可观测性系统的建设将变得尤为重要&#xff0c;而在整个可观测性系统中。 可观测性系统 在任何有一定规模的企业内部&#xff0c;一旦推行起来整个SRE的运维模式&#xff0c;那么对于…

python初探

python近两年似乎已经很热了&#xff0c;不了解一下怎么能行呢&#xff0c;似乎python最大的优点就是简洁、易懂、优雅。目前豆瓣、知乎等后台服务使用的也都是python语言。 python一般可以用于网站服务、小工具、数据分析等工作。它作为高级语言&#xff0c;和js一样&#xff…

Linux系统PATH变量配置

alias命令用于设置命令的别名&#xff0c;格式为“alias 别名命令” 例如担心复制文件时误将文件被覆盖&#xff0c;可以执行alias cp" cp -i"&#xff0c;如此一来 每次复制命令都会询问用户是否要覆盖。 unalias命令用于取消命令的别名&#xff1a;格式为"una…

solr5.5索引mysql数据(新手总结)

一 solr5.5环境部署到Eclipse(luna版&#xff09; solr部署参见&#xff1a;http://blog.csdn.net/csmnjk/article/details/64121765 二 Ik分词器设置 IK分词器设置参见:http://blog.csdn.net/csmnjk/article/details/51693578 solr4版本的schema.xml文件对应solr5版本的manage…

老板加薪!看我做的WPF Loading!!!

老板加薪&#xff01;看我做的WPF Loading&#xff01;&#xff01;&#xff01;控件名&#xff1a;RingLoading作者&#xff1a;WPFDevelopersOrg原文链接&#xff1a; https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal框架使用大于等于.NET40&#xff1b;Visua…

如何避免下重复订单

电子交易的一个很基本的问题&#xff0c;就是避免用户下重复订单。用户明明想买一次&#xff0c;结果一看下了两个单。如果没有及时发现&#xff0c;就会带来额外的物流成本和扯皮。对商家的信誉也不好看。 从技术上看&#xff0c;这是一个分布式一致性问题&#xff1b;但实际…

图像分类学习笔记

1.计算机认识图像的方式&#xff1a;都是数字。例如一个 128X128 的3通道的图片 是由 128X128X3个数字 组成的。 2.面临的难点&#xff1a;一幅图可以说明。 3.分类器 A&#xff1a;Nearest Neighbor Classifier&#xff1a;与CNN无关&#xff0c;但是可以帮助我们理解一下分类…

知物由学 | 干货!一文了解安卓APP逆向分析与保护机制

“知物由学”是网易云易盾打造的一个品牌栏目&#xff0c;词语出自汉王充《论衡实知》。人&#xff0c;能力有高下之分&#xff0c;学习才知道事物的道理&#xff0c;而后才有智慧&#xff0c;不去求问就不会知道。“知物由学”希望通过一篇篇技术干货、趋势解读、人物思考和沉…

[转]以终为始,详细分析高考志愿该怎么填

为什么写这篇文章&#xff1f; 之所以写本文&#xff0c;是因为我自己有用处。 我简要介绍&#xff0c;长话短说。我从一个普通的211本科毕业&#xff0c;已经接受社会"毒打"多年&#xff0c;回想起高考填志愿&#xff0c;依然会觉得有些许遗憾。我在贵州省的一个小县…

ASP.NET Core 中的重定向

前言在《如何使用ASP.NET Core Web API实现短链接服务》中&#xff0c;我们使用了Redirect方法返回跳转状态码:[HttpGet("{shortUrl}")] public IActionResult GetUrl(string shortUrl) {var hashids new Hashids("公众号My IO", minHashLength: 6);var i…

C#IO

System.IO 命名空间包含允许读写文件和数据流的类型以及提供基本文件和目录支持的类型。string url "C:\chisp.log";if (System.IO.File.Exists(url)){Response.Write("文件存在");}else{ Response.Write("文件不存在"); }System.IO.File.Exist…

Lind.DDD.Manager里的3,7,15,31,63,127,255,511,1023,2047

回到目录 进制 我是一个程序猿&#xff0c;我喜欢简单的数字&#xff0c;十进制如何&#xff0c;数字太多&#xff0c;有10种数字组成&#xff0c;但由于它广为人知&#xff0c;所有使用最为广泛&#xff0c;人们的惯性思维培养了十进制&#xff0c;并说它是最容易被计算的数字…

3.20学习内容,字符串与列表

一、字符串类型&#xff1a; 作用&#xff1a;名字&#xff0c;性别&#xff0c;国籍&#xff0c;地址等描述信息 定义&#xff1a;在单引号\双引号\三引号内&#xff0c;由一串字符组成。 需要掌握的方法&#xff1a; 1、strip 去除指定字符lstrip 去除左边指定字符rstri…

客户端应用试用限制设计

1.概要最近接到公司安排的任务给客户端设计一个“试用30天”的一个需求&#xff0c;其功能主要是为了防止客户拿到产品之后不支付尾款继续使用。众所周知靠纯软件想防“盗版”&#xff0c;“限制试用”等做法是行业难题。只要价值足够高一定有人会破解绕过你的所有防线达到免费…

【开发工具之Spring Tool Suite】6、用Spring Tool Suite简化你的开发

如果你是一个喜欢用spring的人&#xff0c;你可能会在欣赏spring的强大功能外&#xff0c;对其各样的配置比较郁闷&#xff0c;尤其是相差较大的版本在配置文件方面会存在差异&#xff0c;当然你可以去花不少的时间去网上查找相关的资料&#xff0c;当你准备使用更高版本spring…

康威定律,作为架构师还不会灵活运用?

Soft skills are always hard than hard skills. 软技能比硬技能难。 老板听说最近流行“微服务”&#xff0c;问架构师咱们的系统要不要来一套&#xff1f;老板又听说最近流行“中台系统”&#xff0c;问架构师咱们要不要搞起来&#xff1f;其实&#xff0c;这些问题不用老板问…

使用onclick跳转到其他页面。使用button跳转到指定url

1. οnclick"javascript:window.location.hrefaa.htm" 2. οnclick"locationURL"3,。 οnclick"window.location.href?id11"转载于:https://www.cnblogs.com/wujixing/p/5856087.html

Avalonia Beta 1对WPF做了很多改进

\看新闻很累&#xff1f;看技术新闻更累&#xff1f;试试下载InfoQ手机客户端&#xff0c;每天上下班路上听新闻&#xff0c;有趣还有料&#xff01;\\\Avalonia将自己定义为“基于WPF&#xff08;使用XAML、数据绑定以及lookless控件等&#xff09;的跨平台.NET UI框架。”在第…

WebView2 通过 PuppeteerSharp 实现RPA获取壁纸 (案例版)

此案例是《.Net WebView2 项目&#xff0c;实现 嵌入 WEB 页面 Chromium内核》文的续集。主要是针对WebView2的一些微软自己封装的不熟悉的API&#xff0c;有一些人已经对 PuppeteerSharp很熟悉了&#xff0c;那么&#xff0c;直接用 PuppeteerSharp的话&#xff0c;那就降低了…

[转]2022 年 Java 行业分析报告

你好&#xff0c;我是看山。 前段时间介绍了从 Java8 到 Java17 每个版本比较有特点的新特性&#xff08;收录在 从小工到专家的 Java 进阶之旅 专栏&#xff09;&#xff0c;今天看到 JRebel 发布了《2022 年 Java 发展趋势和分析》&#xff0c;于是借此分析一下 Java 行业的现…