来源: 宝塔面板搭建自己的网站,并发布公网远程访问_宝塔本地测试-CSDN博客
在layui中使用 jquery 触发select 的 change事件无效(转载)-CSDN博客
来源: 在layui中使用 jquery 触发select 的 change事件无效(转载)-CSDN博客
使用layui.use监听select事件
在使用layui框架时,原生的js onchange无法起效。要使用
layui.use([‘layer’, ‘JQuery’, ‘form’], function () {
var layer = layui.layer,
$ = layui.JQuery,
form = layui.form;
<select lay-filter="demo" lay-verify="required">
<script>
layui.use(['layer', 'jquery', 'form'], function () {
var layer = layui.layer,
$ = layui.jquery,
form = layui.form;
form.on('select(demo)', function(data){
if(data.value == 1){
$("#searchSessionNum").attr("disabled","true");
form.render('select');
}else{
$("#searchSessionNum").removeAttr("disabled");
form.render('select');//select是固定写法 不是选择器
}
});
});
</script>
Failed to load resource: net::ERR_CACHE_READ_FAILURE解决办法-CSDN博客
来源: Failed to load resource: net::ERR_CACHE_READ_FAILURE解决办法-CSDN博客
在使用elasticsearch分词插件时,使用kibana作为客户端管理,突然报错Kibana did not load properly. Check the server output for more information.
打开后台console查看报错Failed to load resource: net::ERR_CACHE_READ_FAILURE。缓存读取失败。
原因是我之前清空了浏览器的缓存,又重启了电脑,导致kibana读取缓存失败。
解决办法:强制再刷新下浏览器缓存就好了。
————————————————
版权声明:本文为CSDN博主「ghjzzhg」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ghjzzhg/article/details/103009276
C#/.NET/.NET Core优秀项目和框架2023年11月简报 - 追逐时光者 - 博客园
来源: C#/.NET/.NET Core优秀项目和框架2023年11月简报 – 追逐时光者 – 博客园
前言
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(公众号每周至少推荐两个优秀的项目和框架当然节假日除外),公众号推文有项目和框架的介绍、功能特点以及部分截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
- 📈C# GitHub趋势榜
- 🎁C#/.NET/.NET Core项目宝库组织
- 🔔C#/.NET/.NET Core优秀项目和框架精选
- 🏗️C#/.NET/.NET Core优秀项目和框架Issues
- 📰C#/.NET/.NET Core优秀项目和框架公众号月刊
简报GitHub开源地址:https://github.com/YSGStudyHards/DotNetGuide/blob/main/docs/DotNet/DotNetProjectMonthly.md
CAP
- 项目简介: CAP 是一个基于 .NET Standard 的 C# 库,它是一种处理分布式事务的解决方案,同样具有 EventBus 的功能,它具有轻量级、易使用、高性能等特点。CAP 是一个EventBus,同时也是一个在微服务或者SOA系统中解决分布式事务问题的一个框架。它有助于创建可扩展,可靠并且易于更改的微服务系统。
- 项目源码地址: https://github.com/dotnetcore/CAP
- 公众号详细介绍: https://mp.weixin.qq.com/s/ONM9bLKidVCS4pAwJbG9tg
ZEQP.WMS
- 项目简介: ZEQP.WMS仓储管理系统使用 Colder.Admin.AntdVue框架进行功能开发,支持Windows、Linux、macOS多操作系统运行。当前使用的SDK版本是.Net Core 3.1,数据库支持MSSQL/MySQL。
- 项目源码地址: https://github.com/awesomedotnetcore/WMS/
- 公众号详细介绍: https://mp.weixin.qq.com/s/oSWqG0JZpdlckOOCWwIQow
HandyControl
- 项目简介: HandyControl是一套WPF控件库,它几乎重写了所有原生样式,同时包含80余款自定义控件。使用HandyControl你可以轻松地创建一个美观的WPF应用程序,从而大大提高开发效率。
- 项目源码地址: https://github.com/handyOrg/HandyControl
- 公众号详细介绍: https://mp.weixin.qq.com/s/6OcSyquHutFOm5IWcgLLBQ
NETCore.Encrypt
- 项目简介: NETCore.Encrypt是.NET Core加密解密工具类库,包括AES、RSA、MD5、SHA1、DES、SHA256、SHA384、SHA512等更多功能。
- 项目源码地址: https://github.com/myloveCc/NETCore.Encrypt
- 公众号详细介绍: https://mp.weixin.qq.com/s/Z7IcCAqnsKXksNgDUXykag
WinMemoryCleaner
- 项目简介: 一款.NET开源的小巧、智能、免费的Windows内存清理工具(使用Windows内存清理工具来优化内存,这样不必浪费时间去重新启动电脑)。
- 项目源码地址: https://github.com/IgorMundstein/WinMemoryCleaner
- 公众号详细介绍: https://mp.weixin.qq.com/s/y3hqs9T_i5ApgjZEHeoxJg
Hello算法
- 项目简介: Hello算法一个开源免费、新手友好的数据结构与算法入门教程。全书采用动画图解,内容清晰易懂、学习曲线平滑,引导初学者探索数据结构与算法的知识地图。源代码可一键运行,帮助读者在练习中提升编程技能,了解算法工作原理和数据结构底层实现。支持 Java, C++, Python, Go, JS, TS, C#, Swift, Rust, Dart, Zig 等语言。
- 项目源码地址: https://github.com/krahets/hello-algo
- 公众号详细介绍: https://mp.weixin.qq.com/s/9lb5iu6tGNiSGcIrf7fQ3A
PaddleOCRSharp
- 项目简介: PaddleOCRSharp 是一个基于百度飞桨PaddleOCR的.NET版本OCR工具类库。项目核心组件PaddleOCR.dll,由C++编写,根据百度飞桨PaddleOCR的C++代码修改并优化而成。目前已经支持C++、.NET、Python、Golang、Rust等开发语言的直接API接口调用。项目包含文本识别、文本检测、表格识别功能。本项目针对小图识别不准的情况下做了优化,比飞桨原代码识别准确率有所提高。包含总模型仅8.6M的超轻量级中文OCR,单模型支持中英文数字组合识别、竖排文本识别、长文本识别。同时支持中英文、纯英文以及多种语言文本检测识别。
- 项目源码地址: https://gitee.com/raoyutian/paddle-ocrsharp
- 公众号详细介绍: https://mp.weixin.qq.com/s/9F_rSB8Wm69jLdgsH4ufvg
MrHuo.OAuth
- 项目简介: MrHuo.OAuth是.NET项目集成OAuth2登录最全面的、最方便的框架,集成了国内外大部分平台(.NET Core 项目或 .NET Framework 4.6 项目均可使用)。
- 项目源码地址: https://github.com/mrhuo/MrHuo.OAuth
- 公众号详细介绍: https://mp.weixin.qq.com/s/2wTmSz-qPOWPCipIrIWvkg
CoreShop
- 项目简介: 核心商城系统(CoreShop) 是基于 ASP.NET 7.0、Uni-App开发、支持可视化布局的小程序商城系统;前后端分离,支持分布式部署,跨平台运行;拥有分销、代理、团购秒杀、接龙、拼团、直播、优惠券、自定义表单等众多营销功能,拥有完整SKU、下单、售后、物流流程,支持可视化自定义首页模块布局效果。
- 项目源码地址: https://github.com/CoreUnion/CoreShop
- 公众号详细介绍: https://mp.weixin.qq.com/s/iRxmWUXrqArZD_Ax4i6wwg
ToastFish
- 项目简介: 一个C#开源且免费的能利用Windows通知栏背单词的软件,可以让你在上班、上课等恶劣环境下安全隐蔽地背单词(利用摸鱼时间背单词的软件)。
- 项目源码地址: https://github.com/Uahh/ToastFish
- 公众号详细介绍: https://mp.weixin.qq.com/s/CS4NxQMN18LxyBIoEx4RIA
DotNetGuide技术社区交流群
- DotNetGuide技术社区是一个面向.NET开发者的开源技术社区,旨在为开发者们提供全面的C#/.NET/.NET Core相关学习资料、技术分享和咨询、项目推荐、招聘资讯和解决问题的平台。
- 在这个社区中,开发者们可以分享自己的技术文章、项目经验、遇到的疑难技术问题以及解决方案,并且还有机会结识志同道合的开发者。
- 我们致力于构建一个积极向上、和谐友善的.NET技术交流平台,为广大.NET开发者带来更多的价值和成长机会。
.NET8 依赖注入 - xiaolipro - 博客园
来源: .NET8 依赖注入 – xiaolipro – 博客园
依赖注入(Dependency Injection,简称DI)是一种设计模式,用于解耦组件(服务)之间的依赖关系。它通过将依赖关系的创建和管理交给外部容器来实现,而不是在组件(服务)内部直接创建依赖对象。
咱就是通过 IServiceCollection
和 IServiceProvider
来实现的,他们直接被收入到了runtime libraries,在整个.NET平台下通用!
3.1 ServiceCollection
IServiceCollection
本质是一个 ServiceDescriptor
而 ServiceDescriptor
则是用于描述服务类型,实现和生命周期
public interface IServiceCollection :
IList<ServiceDescriptor>,
ICollection<ServiceDescriptor>,
IEnumerable<ServiceDescriptor>,
IEnumerable;
官方提供一些列拓展帮助我们向服务容器中添加服务描述,具体在 ServiceCollectionServiceExtensions
builder.Services.AddTransient<StudentService>();
builder.Services.AddKeyedTransient<IStudentRepository, StudentRepository>("a");
builder.Services.AddKeyedTransient<IStudentRepository, StudentRepository2>("b");
builder.Services.AddTransient<TransientService>();
builder.Services.AddScoped<ScopeService>();
builder.Services.AddSingleton<SingletonService>();
3.2 ServiceProvider
IServiceProvider
定义了一个方法 GetService
,帮助我们通过给定的服务类型,获取其服务实例
public interface IServiceProvider
{
object? GetService(Type serviceType);
}
下面是 GetService
的默认实现(如果不给定engine scope,则默认是root)
public object? GetService(Type serviceType) => GetService(ServiceIdentifier.FromServiceType(serviceType), Root);
也就是
internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}
// 获取服务标识符对应的服务访问器
ServiceAccessor serviceAccessor = _serviceAccessors.GetOrAdd(serviceIdentifier, _createServiceAccessor);
// 执行解析时的hock
OnResolve(serviceAccessor.CallSite, serviceProviderEngineScope);
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);
// 通过服务访问器提供的解析服务,得到服务实例
object? result = serviceAccessor.RealizedService?.Invoke(serviceProviderEngineScope);
System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceIdentifier));
return result;
}
其中,服务标识符 ServiceIdentifier
其实就是包了一下服务类型,和服务Key(为了.NET8的键化服务)
internal readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier>
{
public object? ServiceKey { get; }
public Type ServiceType { get; }
}
显而易见的,我们的服务解析是由 serviceAccessor.RealizedService
提供,而创建服务访问器 serviceAccessor
只有一个实现 CreateServiceAccessor
private ServiceAccessor CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
{
// 通过 CallSiteFactory 获取服务的调用点(CallSite),这是服务解析的一个表示形式。
ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceIdentifier, new CallSiteChain());
// 如果调用站点不为空,则继续构建服务访问器。
if (callSite != null)
{
DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceIdentifier.ServiceType, callSite);
// 触发创建调用站点的相关事件。
OnCreate(callSite);
// 如果调用站点的缓存位置是根(Root),即表示这是一个单例服务。
if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
{
// 直接拿缓存内容
object? value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
return new ServiceAccessor { CallSite = callSite, RealizedService = scope => value };
}
// 通过引擎解析
Func<ServiceProviderEngineScope, object?> realizedService = _engine.RealizeService(callSite);
return new ServiceAccessor { CallSite = callSite, RealizedService = realizedService };
}
// 如果调用点为空,则它的实现服务函数总是返回 null。
return new ServiceAccessor { CallSite = callSite, RealizedService = _ => null };
}
3.2.1 ServiceProviderEngine
ServiceProviderEngine
是服务商解析服务的执行引擎,它在服务商被初始化时建立。有两种引擎,分别是动态引擎和运行时引擎,在 NETFRAMEWORK || NETSTANDARD2_0 默认使用动态引擎。
private ServiceProviderEngine GetEngine()
{
ServiceProviderEngine engine;
#if NETFRAMEWORK || NETSTANDARD2_0
engine = CreateDynamicEngine();
#else
if (RuntimeFeature.IsDynamicCodeCompiled && !DisableDynamicEngine)
{
engine = CreateDynamicEngine();
}
else
{
// Don't try to compile Expressions/IL if they are going to get interpreted
engine = RuntimeServiceProviderEngine.Instance;
}
#endif
return engine;
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "CreateDynamicEngine won't be called when using NativeAOT.")] // see also https://github.com/dotnet/linker/issues/2715
ServiceProviderEngine CreateDynamicEngine() => new DynamicServiceProviderEngine(this);
}
由于.NET Aot技术与dynamic技术冲突,因此Aot下只能使用运行时引擎,但动态引擎在大多情况下仍然是默认的。
动态引擎使用了emit技术,这是一个动态编译技术,而aot的所有代码都需要在部署前编译好,因此运行时无法生成新的代码。那运行时引擎主要使用反射,目标是在不牺牲太多性能的情况下,提供一个在aot环境中可行的解决方案。
我们展开动态引擎来看看它是如何解析服务的。
public override Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite)
{
// 定义一个局部变量来跟踪委托被调用的次数
int callCount = 0;
return scope =>
{
// 当委托被调用时,先使用CallSiteRuntimeResolver.Instance.Resolve方法来解析服务。这是一个同步操作,确保在编译优化之前,服务可以被正常解析。
var result = CallSiteRuntimeResolver.Instance.Resolve(callSite, scope);
// 委托第二次被调用,通过UnsafeQueueUserWorkItem在后台线程上启动编译优化
if (Interlocked.Increment(ref callCount) == 2)
{
// 将一个工作项排队到线程池,但不捕获当前的执行上下文。
_ = ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
try
{
// 用编译优化后的委托替换当前的服务访问器,主要用到emit/expression技术
_serviceProvider.ReplaceServiceAccessor(callSite, base.RealizeService(callSite));
}
catch (Exception ex)
{
DependencyInjectionEventSource.Log.ServiceRealizationFailed(ex, _serviceProvider.GetHashCode());
Debug.Fail($"We should never get exceptions from the background compilation.{Environment.NewLine}{ex}");
}
},
null);
}
return result;
};
}
这个实现的关键思想是,第一次解析服务时使用一个简单的运行时解析器,这样可以快速返回服务实例。然后,当服务再被解析,它会在后台线程上启动一个编译过程,生成一个更高效的服务解析委托。一旦编译完成,新的委托会替换掉原来的委托,以后的服务解析将使用这个新的、更高效的委托。这种方法可以在不影响应用程序启动时间的情况下,逐渐优化服务解析的性能。
3.2.2 ServiceProviderEngineScope
ServiceProviderEngineScope
闪亮登场,他是我们服务商的代言人,从定义不难看出他对外提供了服务商所具备的一切能力
internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IKeyedServiceProvider, IAsyncDisposable, IServiceScopeFactory
{
// this scope中所有实现IDisposable or IAsyncDisposable的服务
private List<object>? _disposables;
// 解析过的服务缓存(其实就是scope生命周期的服务缓存,singleton生命周期的服务缓存都直接挂在调用点上了)
internal Dictionary<ServiceCacheKey, object?> ResolvedServices { get; }
// 实锤服务商代言人
public IServiceProvider ServiceProvider => this;
// 没错啦,通过root scope我们又能继续创建无数个scope,他们彼此独立
public IServiceScope CreateScope() => RootProvider.CreateScope();
}
我们来观察他获取服务的逻辑,可以发现他就是很朴实无华的用着我们根服务商 ServiceProvider
,去解析服务,那 engine scope 呢,就是 this。现在我们已经隐约可以知道engine scope,就是为了满足scope生命周期而生。而 ResolvedServices
中存的呢,就是该scope中的所有scope生命周期服务实例啦,在这个scope中他们是唯一的。
public object? GetService(Type serviceType)
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}
return RootProvider.GetService(ServiceIdentifier.FromServiceType(serviceType), this);
}
再来看另一个核心的方法:CaptureDisposable
,实现disposable的服务会被添加到 _disposables。
internal object? CaptureDisposable(object? service)
{
// 如果服务没有实现 IDisposable or IAsyncDisposable,那么不需要捕获,直接原路返回
if (ReferenceEquals(this, service) || !(service is IDisposable || service is IAsyncDisposable))
{
return service;
}
bool disposed = false;
lock (Sync)
{
if (_disposed) // 如果scope已经销毁则进入销毁流程
{
disposed = true;
}
else
{
_disposables ??= new List<object>();
_disposables.Add(service);
}
}
// Don't run customer code under the lock
if (disposed) // 这表示我们在试图捕获可销毁服务时,scope就已经被销毁
{
if (service is IDisposable disposable)
{
disposable.Dispose();
}
else
{
// sync over async, for the rare case that an object only implements IAsyncDisposable and may end up starving the thread pool.
object? localService = service; // copy to avoid closure on other paths
Task.Run(() => ((IAsyncDisposable)localService).DisposeAsync().AsTask()).GetAwaiter().GetResult();
}
// 这种case会抛出一个ObjectDisposedException
ThrowHelper.ThrowObjectDisposedException();
}
return service;
}
在engine scope销毁时,其作用域中所有scope生命周期且实现了disposable的服务(其实就是_disposable)呢,也会被一同的销毁。
public ValueTask DisposeAsync()
{
List<object>? toDispose = BeginDispose(); // 获取_disposable
if (toDispose != null)
{
// 从后往前,依次销毁服务
}
}
那么有同学可能就要问了:单例实例既然不存在root scope中,而是单独丢到了调用点上,那他是咋销毁的?压根没看到啊,那不得泄露了?
其实呀,同学们并不用担心这个问题。首先,单例服务的实例确实是缓存在调用点上,但 disable 服务仍然会被 scope 捕获呀(在下文会详细介绍)。在 BeginDispose 中的,我们会去判断,如果是 singleton case,且root scope 没有被销毁过,我们会主动去销毁喔~
if (IsRootScope && !RootProvider.IsDisposed()) RootProvider.Dispose();
3.3 ServiceCallSite
ServiceCallSite
的主要职责是封装服务解析的逻辑,它可以代表一个构造函数调用、属性注入、工厂方法调用等。DI系统使用这个抽象来表示服务的各种解析策略,并且可以通过它来生成服务实例。
internal abstract class ServiceCallSite
{
protected ServiceCallSite(ResultCache cache)
{
Cache = cache;
}
public abstract Type ServiceType { get; }
public abstract Type? ImplementationType { get; }
public abstract CallSiteKind Kind { get; }
public ResultCache Cache { get; }
public object? Value { get; set; }
public object? Key { get; set; }
public bool CaptureDisposable => ImplementationType == null ||
typeof(IDisposable).IsAssignableFrom(ImplementationType) ||
typeof(IAsyncDisposable).IsAssignableFrom(ImplementationType);
}
3.3.1 ResultCache
其中 ResultCache
定义了我们如何缓存解析后的结果
public CallSiteResultCacheLocation Location { get; set; } // 缓存位置
public ServiceCacheKey Key { get; set; } // 服务key(键化服务用的)
CallSiteResultCacheLocation
是一个枚举,定义了几个值
Root
:表示服务实例应该在根级别的IServiceProvider
中缓存。这通常意味着服务实例是单例的(Singleton),在整个应用程序的生命周期内只会创建一次,并且在所有请求中共享。Scope
:表示服务实例应该在当前作用域(Scope)中缓存。对于作用域服务(Scoped),实例会在每个作用域中创建一次,并在该作用域内的所有请求中共享。Dispose
:尽管这个名称可能会让人误解,但在ResultCache
的上下文中,Dispose
表示着服务是瞬态的(每次请求都创建新实例)。None
:表示没有缓存服务实例。
ServiceCacheKey
结构体就是包了一下服务标识符和一个slot,用于适配多实现的
internal readonly struct ServiceCacheKey : IEquatable<ServiceCacheKey>
{
public ServiceIdentifier ServiceIdentifier { get; }
public int Slot { get; } // 那最后一个实现的slot是0
}
3.3.2 CallSiteFactory.GetCallSite
那我们来看看调用点是怎么创建的吧,其实上面已经出现过一次了:
private ServiceCallSite? CreateCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
{
if (!_stackGuard.TryEnterOnCurrentStack()) // 防止栈溢出
{
return _stackGuard.RunOnEmptyStack(CreateCallSite, serviceIdentifier, callSiteChain);
}
// 获取服务标识符对应的锁,以确保在创建调用点时的线程安全。
// 是为了保证并行解析下的调用点也只会被创建一次,例如:
// C -> D -> A
// E -> D -> A
var callsiteLock = _callSiteLocks.GetOrAdd(serviceIdentifier, static _ => new object());
lock (callsiteLock)
{
// 检查当前服务标识符是否会导致循环依赖
callSiteChain.CheckCircularDependency(serviceIdentifier);
// 首先尝试创建精确匹配的服务调用站点,如果失败,则尝试创建开放泛型服务调用站点,如果还是失败,则尝试创建枚举服务调用站点。如果所有尝试都失败了,callSite将为null。
ServiceCallSite? callSite = TryCreateExact(serviceIdentifier, callSiteChain) ??
TryCreateOpenGeneric(serviceIdentifier, callSiteChain) ??
TryCreateEnumerable(serviceIdentifier, callSiteChain);
return callSite;
}
}
那服务点的创建过程我就简单概述一下啦
- 查找调用点缓存,存在就直接返回啦
- 服务标识符会被转成服务描述符
ServiceDescriptor
(key化服务不指定key默认取last) - 计算
ServiceCallSite
,依次是:- TryCreateExact
- 计算
ResultCache
- 如果已经有实现实例了,则返回
ConstantCallSite
:表示直接返回已经创建的实例的调用点。 - 如果有实现工厂,则返回
FactoryCallSite
:表示通过工厂方法创建服务实例的调用点。 - 如果有实现类型,则返回
ConstructorCallSite
:表示通过构造函数创建服务实例的调用点。
- 计算
- TryCreateOpenGeneric
- 根据泛型定义获取服务描述符
ServiceDescriptor
- 计算
ResultCache
- 使用服务标识符中的具体泛型参数来构造实现的闭合类型
- AOT兼容性测试(因为不能保证值类型泛型的代码已经生成)
- 如果成功闭合,则返回
ConstructorCallSite
:表示通过构造函数创建服务实例的调用点。
- 根据泛型定义获取服务描述符
- TryCreateEnumerable
- 确定类型是
IEnumerable<T>
- AOT兼容性测试(因为不能保证值类型数组的代码已经生成)
- 如果
T
不是泛型类型,并且可以找到对应的服务描述符集合,则循环 TryCreateExact - 否则,方向循环 TryCreateExact,然后方向循环 TryCreateOpenGeneric
- 确定类型是
- TryCreateExact
3.4 CallSiteVisitor
好了,有了上面的了解我们可以开始探索服务解析的内幕了。服务解析说白了就是引擎围着 CallSiteVisitor
转圈圈,所谓的解析服务,其实就是访问调用点了。
protected virtual TResult VisitCallSite(ServiceCallSite callSite, TArgument argument)
{
if (!_stackGuard.TryEnterOnCurrentStack()) // 一些校验,分栈啥的
{
return _stackGuard.RunOnEmptyStack(VisitCallSite, callSite, argument);
}
switch (callSite.Cache.Location)
{
case CallSiteResultCacheLocation.Root: // 单例
return VisitRootCache(callSite, argument);
case CallSiteResultCacheLocation.Scope: // 作用域
return VisitScopeCache(callSite, argument);
case CallSiteResultCacheLocation.Dispose: // 瞬态
return VisitDisposeCache(callSite, argument);
case CallSiteResultCacheLocation.None: // 不缓存(ConstantCallSite)
return VisitNoCache(callSite, argument);
default:
throw new ArgumentOutOfRangeException();
}
}
为了方便展示,我们这里的解析器都拿运行时来说,因为内部是反射,而emit、expression实在是难以观看。
3.4.1 VisitRootCache
那我们来看看单例的情况下,是如何访问的:
protected override object? VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
if (callSite.Value is object value)
{
// Value already calculated, return it directly
return value;
}
var lockType = RuntimeResolverLock.Root;
// 单例都是直接放根作用域的
ServiceProviderEngineScope serviceProviderEngine = context.Scope.RootProvider.Root;
lock (callSite)
{
// 这里搞了个双检锁来确保在多线程环境中,同一时间只有一个线程可以执行接下来的代码块。
// Lock the callsite and check if another thread already cached the value
if (callSite.Value is object callSiteValue)
{
return callSiteValue;
}
object? resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
{
Scope = serviceProviderEngine,
AcquiredLocks = context.AcquiredLocks | lockType
});
// 捕获可销毁的服务
serviceProviderEngine.CaptureDisposable(resolved);
// 缓存解析结果到调用点上
callSite.Value = resolved;
return resolved;
}
}
好,可以看到真正解析调用点的主角出来了 VisitCallSiteMain
,那这里的 CallSiteKind
上面计算 ServiceCallSite
时呢已经讲的很清楚啦,咱对号入座就行了
protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
{
switch (callSite.Kind)
{
case CallSiteKind.Factory:
return VisitFactory((FactoryCallSite)callSite, argument);
case CallSiteKind.IEnumerable:
return VisitIEnumerable((IEnumerableCallSite)callSite, argument);
case CallSiteKind.Constructor:
return VisitConstructor((ConstructorCallSite)callSite, argument);
case CallSiteKind.Constant:
return VisitConstant((ConstantCallSite)callSite, argument);
case CallSiteKind.ServiceProvider:
return VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
default:
throw new NotSupportedException(SR.Format(SR.CallSiteTypeNotSupported, callSite.GetType()));
}
}
我们就看看最经典的通过构造函数创建服务实例的调用点 ConstructorCallSite
,很显然就是new嘛,只不过可能构造中依赖其它服务,那就递归创建呗。easy,其它几种太简单了大家自己去看看吧。
protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
{
object?[] parameterValues;
if (constructorCallSite.ParameterCallSites.Length == 0)
{
parameterValues = Array.Empty<object>();
}
else
{
parameterValues = new object?[constructorCallSite.ParameterCallSites.Length];
for (int index = 0; index < parameterValues.Length; index++)
{
// 递归构建依赖的服务
parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context);
}
}
// new (xxx)
return constructorCallSite.ConstructorInfo.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameterValues, culture: null);
}
3.4.2 VisitScopeCache
在访问单例缓存的时候呢,仅仅通过了一个double check lock就搞定了,因为人家全局的嘛,咱再来看看访问作用域缓存,会不会有什么不一样
protected override object? VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
// Check if we are in the situation where scoped service was promoted to singleton
// and we need to lock the root
return context.Scope.IsRootScope ?
VisitRootCache(callSite, context) :
VisitCache(callSite, context, context.Scope, RuntimeResolverLock.Scope);
}
哈哈,它果然很不一般啊,上来就来检查我们是否是 root scope。如果是这种case呢,则走 VisitRootCache
。但是奇怪啊,为什么访问 scope cache,所在 engine scope 能是 root scope?
还记得 ServiceProvider
获取的服务实例的核心方法吗?engine scope 他是传进来的,如果我们给一个 root scope,自然就出现的这种case,只是这种 case 特别罕见。
internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
VisitCache
的同步模型写的实在是酷,我们看 RuntimeResolverLock
的枚举就两个:Scope = 1
和 Root = 2
- AcquiredLocks=Scope时
- 那 AcquiredLocks&false==0 显然成立,申请锁,也就是尝试独占改作用域的ResolvedServices
- 申请成功进入同步块,重新计算AcquiredLocks|true=1
- 如此,在该engine scope 中这条链路上的调用点都被占有,直到结束
- AcquiredLocks=Root 时
- 那显然 engine scope 也应该是 root scope,那么走
VisitRootCache
case - 在
VisitRootCache
通过DCL锁住 root scope 上链路涉及的服务点,直至结束
- 那显然 engine scope 也应该是 root scope,那么走
至此我们应该不难看出这个设计的精妙之处,即在非 root scope(scope生命周期)中,scope之间是互相隔离的,没有必要像 root scope(singleton生命周期)那样,在所有scope中独占服务点。
private object? VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine
{
bool lockTaken = false;
object sync = serviceProviderEngine.Sync;
Dictionary<ServiceCacheKey, object?> resolvedServices = serviceProviderEngine.ResolvedServices;
if ((context.AcquiredLocks & lockType) == 0)
{
Monitor.Enter(sync, ref lockTaken);
}
try
{
// Note: This method has already taken lock by the caller for resolution and access synchronization.
// For scoped: takes a dictionary as both a resolution lock and a dictionary access lock.
if (resolvedServices.TryGetValue(callSite.Cache.Key, out object? resolved))
{
return resolved;
}
// scope服务的解析结果是放在engine scope的ResolvedServices上的,而非调用点
resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
{
Scope = serviceProviderEngine,
AcquiredLocks = context.AcquiredLocks | lockType
});
serviceProviderEngine.CaptureDisposable(resolved);
resolvedServices.Add(callSite.Cache.Key, resolved);
return resolved;
}
finally
{
if (lockTaken)
{
Monitor.Exit(sync);
}
}
}
3.4.3 VisitDisposeCache
我们看最后一个,也就是 Transient
case
protected override object? VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
{
return context.Scope.CaptureDisposable(VisitCallSiteMain(transientCallSite, context));
}
异常的简单,我们沿用了scope的设计,但是我们没有进行任何缓存行为。即,每次都去访问调用点。
【ASP.NET Core】MVC过滤器:常见用法 - 东邪独孤 - 博客园
来源: 【ASP.NET Core】MVC过滤器:常见用法 – 东邪独孤 – 博客园
前面老周给大伙伴们演示了过滤器的运行流程,大伙只需要知道下面知识点即可:
1、过滤器分为授权过滤、资源访问过滤、操作方法(Action)过滤、结果过滤、异常过滤、终结点过滤。上一次咱们没有说异常过滤和终结点过滤,不过老周后面会说的。对这些过滤器,你有印象就行了。
2、所有过滤器接口都有同步版本和异步版本。为了让伙伴不要学得太累,咱们暂时只说同步版本的。
3、过滤器的应用可以分为全局和局部。全局先运行,局部后运行。全局在应用程序初始化时配置,局部用特性类来配置。
4、实际应用中,我们不需要实现所有过滤器接口,需要啥就实现啥即可。比如,你想在 Action 调用后修改一些东西,那实现 IActionFilter 接口就好了,其他不用管。
本篇咱们的重点在于“用”,光知道是啥是不行的,得拿来用才是硬道理。
我们先做第一个练习:阻止控制器的参数从查询字符串获取数据。
什么意思呢?咱们知道,MVC 在模型绑定时,会从 N 个 ValueProvider 中提取数据值,包括 QueryString、Forms、RouteData 等。其中,QueryString 就是URL的查询字符串。比如,咱们写一个这样的控制器:
public class GameController : ControllerBase { [HttpGet("game/play")] public string Play(Game g) { if(ModelState.IsValid == false) { return "你玩个寂寞"; } return $"你正在玩{g.Year}年出的《{g.GameName}》游戏"; } } public class Game { /// <summary> /// 游戏序列号 /// </summary> public string? GameSerial { get; set; } /// <summary> /// 游戏名称 /// </summary> public string? GameName { get; set; } /// <summary> /// 谁发行的 /// </summary> public string? Publisher { get; set; } /// <summary> /// 哪一年发行的 /// </summary> public int Year { get; set; } }
这个通过 /game/play?gameserial=DDSBYCL-5K2FF&gamename=伏地魔三世&publisher=无德无能科技有限公司&year=2017 这样的URL就能传递数据给 g 参数。
这里我不做 HTML 页了,直接通过 MapGet 返回 HTML 内容。
app.MapGet("/", async (HttpContext context) => { string html = """ <!DOCTYPE html> <html> <head> <title>试试看</title> <style> label { min-width: 100px; display: inline-block; } </style> </head> <body> <div> <label for="gserial">游戏序列号:</label> <input id="gserial" type="text" /> </div> <div> <label for="gname">游戏名称:</label> <input id="gname" type="text" /> </div> <div> <label for="pub">发行者:</label> <input type="text" id="pub" /> </div> <div> <label for="year">发行年份:</label> <input id="year" type="text"/> </div> <div> <button onclick="reqTest()">确定</button> </div> <p id="res"></p> <script> function reqTest() { let serial= document.getElementById("gserial").value; let name = document.getElementById("gname").value; let pub = document.getElementById("pub").value; let year = parseInt(document.getElementById("year").value, 10); let result = document.getElementById("res"); const url = `/game/play?gameSerial=${serial}&gamename=${name}&publisher=${pub}&year=${year}`; fetch(url, { method: "GET" }) .then(response => { response.text().then(txt => { result.innerHTML = txt; }); }); } </script> </body> </html> """; var response = context.Response; response.Headers.ContentType = "text/html; charset=UTF-8"; await response.WriteAsync(html); });
设置响应的 Content-Type 头时一定要指定字符集是 UTF-8 编码,这样可以免去 99.999% 的乱码问题。向服务器发送请求是通过 fetch 函数实现的。
比如,咱们在页面上填写:
然后点一下“确定”按钮,提交成功后服务将响应:
你正在玩2022年出的《法外狂徒大冒险》游戏
模型绑定的数据是从查询字符串提取出来的。现在,咱们写一个过滤器,阻止 QueryStringValueProvider 提供查询字符串数据。而 QueryStringValueProvider 实例是由 QueryStringValueProviderFactory 工厂类负责创建的。因此,需要写一个过滤器,在模型绑定之前删除 QueryStringValueProviderFactory 对象,这样模型绑定时就不会读取 URL 中的查询字符串了。
于是,重点就落在选用哪种过滤器。关键点是:必须在模型绑定前做这项工作。所以,Action过滤器、结果过滤器就别指望了,而且肯定不是授权过滤器,那就剩下资源过滤器了。
咱们写一个自定义的资源过滤器—— RemoveQueryStringProviderFilter,实现的接口当然是 IResourceFilter 了。
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; public class RemoveQueryStringProviderFilter : IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { // 空空如也 } public void OnResourceExecuting(ResourceExecutingContext context) { var qsValueProviders = context.ValueProviderFactories.OfType<QueryStringValueProviderFactory>(); if (qsValueProviders != null && qsValueProviders.Any()) { context.ValueProviderFactories.RemoveType<QueryStringValueProviderFactory>(); } } }
我们要做的事情是在模型绑定之前才有效,所以 OnResourceExecuted 方法不用管,留白即可。在 OnResourceExecuting 方法中,首先用 ValueProviderFactories.OfType<T> 方法找出现有的 QueryStringValueProviderFactory 对象,若找到,用 RemoveType 方法删除。
把这个过滤器应用于全局。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => { options.Filters.Add<RemoveQueryStringProviderFilter>(); }); var app = builder.Build();
现在,咱们再运行应用程序,输入游戏信息。
点击“确定”按钮后,发现服务未响应正确的内容。
你正在玩0年出的《》游戏
由于无法从查询字符串中提取到数据,所以返回默认属性值。为什么不是返回“你玩个寂寞”呢?因为模型绑定并没有出错,也未出现验证失败的值,只是未提供值而已,即 ModelState.IsValid 的值依然是 true 的。
下面咱们看看异常过滤器怎么用。
异常过滤器最好定义为局部的,而非全局。使用局部过滤器的好处是针对性好,全局的话你用一个万能异常处理好像过于简单。当然了,如果你的项目可以这样做,就随便用。
这里我定义一个局部的异常过滤器,通过特性方式应用到操作方法上。
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class CustExceptionFilterAttribute : Attribute, IExceptionFilter { public void OnException(ExceptionContext context) { // 看看有没有异常 if (context.Exception is null || context.ExceptionHandled) return; // 有异常哦,修改一下返回结果 ContentResult result = new(); result.Content = "出错啦,伙计。错误消息:" + context.Exception.Message; // 设置返回结果 context.Result = result; // 标记异常已处理 context.ExceptionHandled = true; } }
首先,context 参数是一种上下文对象,它在多上异常过滤器间共享实例(你可能实现了很多个异常过滤器)。context.Exception 属性引用的是异常类对象;注意一个有趣的属性 ExceptionHandled,它是一个 bool 类型的值,表示“我这个异常过滤器是否已处理过了”。这个有啥用呢?由于上下文是共享的,当你的某个异常过滤器设置了 ExceptionHandled 为 true,那么,其他异常过滤器也可以读这个属性。这样就可以实现:啊,原来有人处理过这个异常了,那我就不处理了。即 A 过滤器处理异常后设置为已处理,B 过滤器可以检查这个属性的值,如果没必要处理就跳过。
下面写一个控制器,并在方法成员上应用前面定义的异常过滤器。
public class AbcController : ControllerBase { [HttpGet("calc/add"), CustExceptionFilter] public int Add(int x, int y) { int r = x + y; if(r >= 1000) { throw new Exception("计算结果必须在1000以内"); } return r; } }
此处我设定抛出异常的条件是:x、y 相加结果大于或等于 1000。
咱们以 GET 方式调用,URL 为 /calc/add?x=599&y=699,这样会发生异常。服务器的响应为:
最后一个例子是结果过滤器。咱们要实现在响应消息中添加自定义 Cookie。
public class SetCookieResultFilter : IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { // 不能在这里写 Cookie } public void OnResultExecuting(ResultExecutingContext context) { HttpResponse response = context.HttpContext.Response; // 设置Cookie response.Cookies.Append("X-VER", "3.0"); } }
这里要注意,咱们要写 Cookie 必须在 OnResultExecuting 方法中处理,不能在 OnResultExecuted 方法中写。因为当 OnResultExecuted 方法执行时,响应消息头已经被锁定了,Cookie 是通过 set-cookie 标头实现的,此时无法修改 HTTP 头了,写 Cookie 会抛异常。所以只能在 OnResultExecuting 方法中写。
定义一个控制器。
public class DemoController : ControllerBase { [HttpGet("abc/test")] public string Work() => "山重水复疑无路"; }
将自定义的结果过滤器添加为全局。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => { options.Filters.Add<SetCookieResultFilter>(); }); var app = builder.Build();
好,试试看。
Layui实现图片列表并且可以放大查看 - Core、陈 - 博客园
来源: Layui实现图片列表并且可以放大查看 – Core、陈 – 博客园
首先建一个DIV层
1 <div class="layui-row layui-col-space10"> 2 <div class="layui-col-md12"> 3 <div class="layui-card"> 4 <div class="layui-card-body"> 5 <fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;"> 6 <legend>图片列表</legend> 7 </fieldset> 8 <div class="layui-row layui-col-space30" style="height: 300px; overflow:auto" id="LAY_Images"> 9 </div> 10 </div> 11 </div> 12 </div> 13 </div>
然后写一个数据请求的方法
1 //请求图像数据 2 $.ajax({ 3 url: "接口路径", 4 data: { 'Id': 1 }, 5 type: "post", 6 dataType: "json", 7 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 8 success: function (data) { 9 $("#LAY_Images").empty(); 10 $.each(data.data, function (index, item) { 11 $("#LAY_Images").append( 12 "<div class='layui-col-md2 ew-datagrid-item'>" + 13 "<div class='project-list-item'>" + 14 "<img class='project-list-item-cover' src='" + item.imgname + "' onclick='previewImg(this)' />" + 15 "</div>" + 16 "</div>" 17 ); 18 }) 19 form.render($('#LAY_Images')); 20 }, 21 error: function (data) { 22 23 } 24 });
这样就渲染好图片的列表了,如果不想要放大功能,去掉onclick事件就可以了,如果需要,加上下面的方法
1 //点击图片放大查看 2 function previewImg(obj) { 3 var img = new Image(); 4 img.src = obj.src; 5 var height = img.height; //获取图片高度 6 var width = img.width; //获取图片宽度 7 if (height > 1000 || width > 800) { 8 height = height / 1.5; 9 width = width / 1.5; 10 } 11 var imgHtml = "<img src='" + obj.src + "' style='width: " + width + "px;height:" + height + "px'/>"; 12 //弹出层 13 layer.open({ 14 type: 1, 16 offset: 'auto', 17 area: [width + 'px', height + 'px'], 18 shadeClose: true,//点击外围关闭弹窗 19 scrollbar: true,//不现实滚动条 20 title: false, //不显示标题 21 content: imgHtml, //捕获的元素,注意:最好该指定的元素要存放在body最外层,否则可能被其它的相对元素所影响 22 cancel: function () { 23 24 } 25 }); 26 }
这样基本就可以做出一个图片列表框出来了
原生js使用FileReader将文件转成base64_js filereader base64-CSDN博客
来源: 原生js使用FileReader将文件转成base64_js filereader base64-CSDN博客
一、FileReader介绍
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容。可以使用File对象指定要读取的文件。
File对象可以是来自用户在一个type类型为file的input元素上选择文件后返回的FileList对象。
<input type=”file” name=”” id=””>
注意: FileReader仅用于以安全的方式从用户(远程)系统读取文件内容,它不能用于从文件系统中直接使用路径名简单地读取文件。
二、FileReader的使用
FileReader是一个构造函数,需要new之后拿到FileReader的实例。
var reader = new FileReader();
1
实例的方法:
reader.abort():中止读取操作。
reader.readAsArrayBuffer():读取完成后,触发onload事件,result 属性中保存的是文件的 ArrayBuffer 数据对象。
reader.readAsDataURL():读取完成后,触发onload事件,result属性中保存的是Base64文件内容。
reader.readAsText():读取完成后,触发onload事件,result属性中保存的是字符串文件内容。
实例的事件:
reader.onabort:在读取操作被中断时触发。
reader.onerror:在读取操作发生错误时触发。
reader.onload:在读取操作完成时触发。
reader.onloadstart:在读取操作开始时触发。
reader.onloadend:在读取操作结束时(要么成功,要么失败)触发。
实例的属性:
reader.error:返回读取文件时的错误信息
reader.readyState:表示FileReader状态的数字。0:还没有加载;1:正在加载;2:加载完成
reader.result:文件的内容
三、使用范例
obtn.onclick = function(){
var reader = new FileReader();
reader.readAsDataURL(f.files[0]); // 解析成base64格式
reader.onload = function () {
console.log(this.result); // 解析后的数据,如下图
}
}
obtn.onclick = function(){
var reader = new FileReader();
reader.readAsArrayBuffer(f.files[0]);// 解析成ArrayBuffer格式
reader.onload = function () {
console.log(this.result); // 解析后的数据,如下图
}
}
obtn.onclick = function(){
var reader = new FileReader();
reader.readAsText(f.files[0]); // 解析成Text格式
reader.onload = function () {
console.log(this.result); // 解析后的数据,如下图
}
}
以上,如有错误,请留言交流…
————————————————
版权声明:本文为CSDN博主「杨树林er」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41636483/article/details/112589158
【Windows系统】查看和关闭139、445端口的方法_关闭139端口_jin_study的博客-CSDN博客
来源: 【Windows系统】查看和关闭139、445端口的方法_关闭139端口_jin_study的博客-CSDN博客
文章目录
前言
一、 Windows查看139、445端口的方法
二、关闭445端口的方法
三、关闭139端口的方法
前言
“航天派”公众号上一期文章介绍了“麒麟操作系统查看和关闭139、445端口的方法”,今天介绍“Windows系统查看和关闭139、445端口的方法”。
前期,尝试使用一些软件关闭139、445端口,结果效果不理想;尝试使用网络上的一些操作方法,效果也不佳。今天介绍的关闭端口方法是经过多次实践验证过的,是真实有效的。需要提前说明,使用本文关闭139、445端口的方法,要在操作完成之后进行系统重启才能生效。
一、 Windows查看139、445端口的方法
按Windows+R键,弹出运行窗口,输入cmd回车,然后在命令提示符窗口输入如下命令,查看139、445端口是否存在:
netstat -na | findstr 139
netstat -na | findstr 445
1
2
如下图,可以看到该计算机TCP协议下均存在139、445端口,且处于LISTENING状态,接下来将封闭该计算机的139、445端口。
二、关闭445端口的方法
(1)在命令提升符号窗口接着输入regedit,将弹出注册表编辑器。
(2)在注册表编辑器找到HKEY_LOCAL_MACHINE—>SYSTEM—>ControlSet001—>services,如下图。
(3)在services中,找到NetBT—>Parameters,然后选中Parameters右键新建DWORD(32-位)值,如下图。
(4)将新建的DWORD命名为“SMBDericeEnabled”,并将其值修改为0(默认就是0,可以不用改),如下图。
(5)打开控制面板,选择管理工具—>服务—>Server—属性,如下图。
(6)在属性窗口将启动类型改为“禁用”,将服务状态选为“暂停”,然后点击应用、确定即可,如下图。
(7)完成以上操作之后,重启计算机,再次使用netstat -na | findstr 445查询,发现445端口已经关闭。当然可以等关闭完139端口之后,一起重启计算机,下面介绍关闭139端口的方法。
三、关闭139端口的方法
关闭139端口的方法,相比关闭445端口的方法,要简单很多。
(1)通过控制面板或者直接双击状态栏中网络连接图标,打开“网络和共享中心”,选择“更改适配器设置”—>本地连接—>属性,然后选择Internet协议版本4(TCP/IPv4)—>高级—>WINS—>禁用TCP/IP上的NetBIOS(S),然后点击确定即可,如下图。
(2)完成以上操作之后,重启计算机,然后在命令提示符窗口输入netstat -na | findstr 139、netstat -na | findstr 445查看139、445端口是否已关闭。如下图,经查询,139、445端口已经不存在,已经被关闭。
以上介绍了Windows系统查看和关闭139、445端口的方法,是经过多次实践验证过的,方法靠谱,值得分享。
关注“航天派”微信公众号,了解以下更好实用内容:
————————————————
版权声明:本文为CSDN博主「jin_study」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jin_study/article/details/124198383
MVC将Base64 保存为图片 - 跑着的小强 - 博客园
来源: MVC将Base64 保存为图片 – 跑着的小强 – 博客园
前台传来Base64字符串。本来可以直接保存数据库返回给图片 但是这样对数据库负担太重 传输也费时间。一搬都是存个地址在数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public ActionResult Injpg(string base64str ) { string data=base64str //要处理下字符串 ,之前的要截取掉 不然会报错 byte[] arr = Convert.FromBase64String(data); using (MemoryStream ms = new MemoryStream(arr)) { Bitmap bmp = new Bitmap(ms); string p = "/text.jpg" ; var w = Server.MapPath(p); bmp.Save(w, System.Drawing.Imaging.ImageFormat.Jpeg); //bmp.Save(@"d:\"test.bmp", ImageFormat.Bmp); //bmp.Save(@"d:\"test.gif", ImageFormat.Gif); //bmp.Save(@"d:\"test.png", ImageFormat.Png); ms.Close(); return Content(p); } } |
解锁新技能 哈哈哈 不用后台压缩了,,,整理下代码 搭建微动态数据库表了