tnblog
首页
视频
资源
登录

.net 6 Kubernete Identity Server4 与 Ocelot 网关的服务整合

7090人阅读 2023/1/14 21:05 总访问:3659113 评论:0 收藏:0 手机
分类: .net后台框架

.netcore

.net 6 Kubernete Identity Server4 与 Ocelot 网关的服务整合

前言


当我的项目通过Helm进行打包之后,发现需要对外暴露三个端口,觉得很没有必要,因为网站没有那么大嘛。
一个是SPA前端网页
一个是网关(Ocelot)
还有一个授权服务(Identity Server 4)
所以我就想把授权服务与网关服务进行整合。

Implicit 模式

何为 Implicit 模式?


简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了授权码(code)这个步骤,因此得名。
所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。


但这还不算什么,在这中间我添加了微软的登录方式,而微软的登录方式使用的又是OAuth2.0+OpenID的登录。
所以我们来讲讲OAuth2.0这种模式。

OAuth 2.0


为了理解OAuth 2.0,我举个例子:当我们登录一个网站时,需要获取微软的账户信息,不可能直接将用户名与密码给这个网站让这个网站去登录获取信息,这样相当不安全,并且有可能会导致用户和密码泄露的情况。
所以OAuth就是为了解决这个而诞生的。


OAuth在”客户端”与”服务提供商”之间,设置了一个授权层(authorization layer)。
“客户端”不能直接登录”服务提供商”,只能登录授权层,以此将用户与客户端区分开来。
“客户端”登录授权层所用的令牌(token),与用户的密码不同。
用户可以在登录的时候,指定授权层令牌的权限范围和有效期。


“客户端”登录授权层以后,”服务提供商”根据令牌的权限范围和有效期,向”客户端”开放用户储存的资料。
运行的流程如下图所示:


(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。


不难看出来,上面六个步骤之中,B是关键,即用户怎样才能给于客户端授权。
有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。
而 Implicit 就是其中的一种模式。

OpenID


OpenID,简单来讲是外部登录获取用户的身份信息。

失败的思路一


我的想法是想通过请求Ocelot网关/auth地址转发到IdentityServer4授权服务器进行登录授权,最终以失败告终,里面最根本的问题是:当微软授权页面登录完成之后,需要将结果返回到IdentityServer4进行处理时,验证的cookie没了。
使用的包是:


报了个错:
An unhandled exception occurred while processing the request.
Correlation failed.
当时查看了很多解决这个问题的方法,但都没有效果。
然后翻源码来调试,重写了MicrosoftAccountHandler类调试,发现就是没有Cookie,网上都说没有设置options.SameSite这玩意,屁用没有。
我们可以看到它可以通过Options.CorrelationCookie.Name + correlationId去Cookie里面找这个,然后再通过验证判断是否非空。

  1. public class MyMicrosoftAccessAuthenticationHandler : MicrosoftAccountHandler
  2. {
  3. public MyMicrosoftAccessAuthenticationHandler(IOptionsMonitor<MicrosoftAccountOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
  4. : base(options, logger, encoder, clock)
  5. {
  6. }
  7. protected override bool ValidateCorrelationId(AuthenticationProperties properties)
  8. {
  9. return base.ValidateCorrelationId(properties);
  10. return true;
  11. ArgumentNullException.ThrowIfNull(properties);
  12. if (!properties.Items.TryGetValue(".xsrf", out var correlationId))
  13. {
  14. //Logger.CorrelationPropertyNotFound(Options.CorrelationCookie.Name!);
  15. return false;
  16. }
  17. properties.Items.Remove(".xsrf");
  18. var cookieName = Options.CorrelationCookie.Name + correlationId;
  19. var correlationCookie = Request.Cookies[cookieName];
  20. if (string.IsNullOrEmpty(correlationCookie))
  21. {
  22. //Logger.CorrelationCookieNotFound(cookieName);
  23. return false;
  24. }
  25. var cookieOptions = Options.CorrelationCookie.Build(Context, Clock.UtcNow);
  26. Response.Cookies.Delete(cookieName, cookieOptions);
  27. if (!string.Equals(correlationCookie, "N", StringComparison.Ordinal))
  28. {
  29. //Logger.UnexpectedCorrelationCookieValue(cookieName, correlationCookie);
  30. return false;
  31. }
  32. return true;
  33. }
  34. }
  35. public static class MyMicrosoftAccessAuthenticationHandlerEx
  36. {
  37. public static AuthenticationBuilder AddMyMicrosoftAccount(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<MicrosoftAccountOptions> configureOptions)
  38. {
  39. return builder.AddOAuth<MicrosoftAccountOptions, MyMicrosoftAccessAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
  40. }
  41. }
  1. services.AddAuthentication()
  2. .AddMyMicrosoftAccount("mic", "Microsoft", microsoftOptions =>
  3. {
  4. microsoftOptions.ClientId = ClientId;
  5. microsoftOptions.ClientSecret = ClientSecret;
  6. microsoftOptions.CallbackPath = "/signin-microsoft";
  7. microsoftOptions.AuthorizationEndpoint = $"https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/authorize";
  8. microsoftOptions.TokenEndpoint = $"https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/token";
  9. using (var sp = services.BuildServiceProvider())
  10. {
  11. var logger = sp.GetRequiredService<ILogger<LoggingHttpHandler>>();
  12. microsoftOptions.BackchannelHttpHandler = new LoggingHttpHandler(logger);
  13. }
  14. })


建议大家千万不要有侥幸心理,改为true跳过去万事大吉的心态,跳过去后还有其他报错。
我直接放弃了。

成功的方法二


主要是以网关冒充成授权服务器,并且让授权服务器自己也认为网关是它的授权地址。
首先在本地改网关的转发地址的配置json。

  1. {
  2. "Identity_Config": {
  3. "Authority": "https://localhost:5400/",
  4. "Audience": "ApiOne",
  5. "IsHttps": false,
  6. "ValidateIssuer": true,
  7. "ValidateAudience": false
  8. },
  9. "Routes": [
  10. { // MvcClient
  11. "DownstreamPathTemplate": "/api/{route}",
  12. "DownstreamScheme": "https",
  13. "UpstreamPathTemplate": "/api/{route}",
  14. "UpstreamHeaderTransform": {
  15. "X-Forwarded-For": "{RemoteIpAddress}"
  16. },
  17. "DownstreamHostAndPorts": [
  18. {
  19. "Host": "localhost",
  20. "Port": 5000
  21. }
  22. ],
  23. "DangerousAcceptAnyServerCertificateValidator": true
  24. },
  25. { // signin-oidc
  26. "DownstreamPathTemplate": "/signin-microsoft",
  27. "DownstreamScheme": "https",
  28. "DownstreamHostAndPorts": [
  29. {
  30. "Host": "localhost",
  31. "Port": 7200
  32. }
  33. ],
  34. "UpstreamPathTemplate": "/signin-microsoft",
  35. "UpstreamHeaderTransform": {
  36. "X-Forwarded-For": "{RemoteIpAddress}"
  37. },
  38. "DangerousAcceptAnyServerCertificateValidator": true
  39. },
  40. { // signin-oidc
  41. "DownstreamPathTemplate": "/IdentityCodeAuth/{url}",
  42. "DownstreamScheme": "https",
  43. "DownstreamHostAndPorts": [
  44. {
  45. "Host": "localhost",
  46. "Port": 7200
  47. }
  48. ],
  49. "UpstreamPathTemplate": "/IdentityCodeAuth/{url}",
  50. "UpstreamHeaderTransform": {
  51. "X-Forwarded-For": "{RemoteIpAddress}"
  52. },
  53. "DangerousAcceptAnyServerCertificateValidator": true
  54. },
  55. { // signout-callback-oidc
  56. "DownstreamPathTemplate": "/signout-callback-oidc",
  57. "DownstreamScheme": "https",
  58. "DownstreamHostAndPorts": [
  59. {
  60. "Host": "localhost",
  61. "Port": 7200
  62. }
  63. ],
  64. "UpstreamPathTemplate": "/signout-callback-oidc",
  65. "UpstreamHeaderTransform": {
  66. "X-Forwarded-For": "{RemoteIpAddress}"
  67. },
  68. "DangerousAcceptAnyServerCertificateValidator": true
  69. },
  70. { // IdentityServer
  71. "DownstreamPathTemplate": "/{route}",
  72. "DownstreamScheme": "https",
  73. "DownstreamHostAndPorts": [
  74. {
  75. "Host": "localhost",
  76. "Port": 7200
  77. }
  78. ],
  79. "UpstreamPathTemplate": "/{route}",
  80. "UpstreamHeaderTransform": {
  81. "X-Forwarded-For": "{RemoteIpAddress}"
  82. },
  83. "DangerousAcceptAnyServerCertificateValidator": true
  84. },
  85. {
  86. "UpstreamPathTemplate": "/_vs/{url}",
  87. "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put", "Head", "Options" ],
  88. "DownstreamPathTemplate": "/_vs/{url}",
  89. "DownstreamScheme": "https",
  90. "DownstreamHostAndPorts": [
  91. {
  92. "Host": "localhost",
  93. "Port": 7200
  94. }
  95. ],
  96. "UpstreamHeaderTransform": {
  97. "X-Forwarded-For": "{RemoteIpAddress}"
  98. },
  99. "LoadBalancerOptions": {
  100. "Type": "RoundRobin"
  101. },
  102. "DangerousAcceptAnyServerCertificateValidator": true
  103. },
  104. {
  105. "UpstreamPathTemplate": "/_framework/{url}",
  106. "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put", "Head", "Options" ],
  107. "DownstreamPathTemplate": "/_framework/{url}",
  108. "DownstreamScheme": "https",
  109. "DownstreamHostAndPorts": [
  110. {
  111. "Host": "localhost",
  112. "Port": 7200
  113. }
  114. ],
  115. "UpstreamHeaderTransform": {
  116. "X-Forwarded-For": "{RemoteIpAddress}"
  117. },
  118. "LoadBalancerOptions": {
  119. "Type": "RoundRobin"
  120. },
  121. "DangerousAcceptAnyServerCertificateValidator": true
  122. }
  123. ]
  124. }


localhost:7200就是我的ids4。
X-Forwarded-For这个可以使下面的路由客户端获取真的IP地址。
DangerousAcceptAnyServerCertificateValidator这个可以跳过https证书的验证。
然后在IdentityServer4中,添加一个请求管道,每当请求的时候修改为网关的授权地址。
我这里通过判断是否有这个ExternalUrl变量来决定的授权服务器地址。

  1. if (!Configuration["ExternalUrl"].IsNullOrEmpty())
  2. {
  3. var externalUrl = Configuration["ExternalUrl"];
  4. app.Use(async (context, next) =>
  5. {
  6. context.SetIdentityServerOrigin(externalUrl);
  7. Console.WriteLine($"The Host: {context.GetIdentityServerHost()} Base Url: {context.GetIdentityServerBaseUrl()} Base Origin: {context.GetIdentityServerOrigin()} Request Cookie: {context.GetIdentityServerBasePath()}");
  8. await next();
  9. });
  10. }
  1. # 5400是我的网关地址
  2. "ExternalUrl": "https://localhost:5400"


Azure里面的应用注册里面,重定向的地址为:

  1. https://localhost:5400/IdentityCodeAuth/ExBackLogoutUrl
  2. https://localhost:5400/signin-microsoft


注销的通道为:

  1. https://localhost:5400/signout-oidc


在数据库中的,Ids4中的三张链接关键数据表的信息为:

  1. # 8080是前端SPA
  2. # [ClientCorsOrigins]
  3. https://localhost:8080
  4. http://localhost:8080
  5. https://localhost:5000
  6. # [ClientPostLogoutRedirectUris]
  7. http://localhost:8080/Home
  8. http://localhost:8080/convert-list
  9. https://login.microsoftonline.com/common/oauth2/v2.0/logout
  10. https://localhost:5400/auth/IdentityCodeAuth/Logout
  11. # [ClientRedirectUris
  12. http://localhost:8080/callback.html
  13. http://localhost:8080/silent-renew.html
  14. http://localhost:8080/convert-list


前端的授权地址写成网关地址,这样在本地测试就没啥问题了,有问题还是在群里问吧!
我已经测试了是没问题的。
然后我们如果要放到Kubernetes中的话就要修改一些信息了。

Kubernetes


这里我使用的是Ingress域名的方式,首先修改网关,我这里它是通过环境变量来进行判断在不同的环境下使用不同的json。
然后就是注意服务名和ocelot的权限。

  1. {
  2. "Identity_Config": {
  3. "Authority": "外部域名地址",
  4. "Audience": "ApiOne",
  5. "IsHttps": false,
  6. "ValidateIssuer": true,
  7. "ValidateAudience": false
  8. },
  9. "Routes": [
  10. {
  11. "UpstreamPathTemplate": "/api/{url}",
  12. "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put", "Head", "Options" ],
  13. "DownstreamPathTemplate": "/api/{url}",
  14. "DownstreamScheme": "https",
  15. "ServiceName": "API接口服务",
  16. "UpstreamHeaderTransform": {
  17. "X-Forwarded-For": "{RemoteIpAddress}"
  18. },
  19. "QoSOptions": {
  20. "ExceptionsAllowedBeforeBreaking": 500,
  21. "DurationOfBreak": 1000,
  22. "TimeoutValue": 3000000
  23. }
  24. },
  25. {
  26. "UpstreamPathTemplate": "/swagger/{url}",
  27. "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put", "Head", "Options" ],
  28. "DownstreamPathTemplate": "/swagger/{url}",
  29. "DownstreamScheme": "https",
  30. "ServiceName": "API接口服务",
  31. "UpstreamHeaderTransform": {
  32. "X-Forwarded-For": "{RemoteIpAddress}"
  33. },
  34. "DangerousAcceptAnyServerCertificateValidator": true
  35. },
  36. {
  37. "UpstreamPathTemplate": "/ServerFile/{url}",
  38. "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put", "Head", "Options" ],
  39. "DownstreamPathTemplate": "/ServerFile/{url}",
  40. "DownstreamScheme": "https",
  41. "ServiceName": "API接口服务",
  42. "UpstreamHeaderTransform": {
  43. "X-Forwarded-For": "{RemoteIpAddress}"
  44. },
  45. "DangerousAcceptAnyServerCertificateValidator": true
  46. },
  47. {
  48. "UpstreamPathTemplate": "/_framework/{url}",
  49. "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put", "Head", "Options" ],
  50. "DownstreamPathTemplate": "/_framework/{url}",
  51. "DownstreamScheme": "https",
  52. "ServiceName": "ids4授权服务",
  53. "UpstreamHeaderTransform": {
  54. "X-Forwarded-For": "{RemoteIpAddress}"
  55. },
  56. "DangerousAcceptAnyServerCertificateValidator": true
  57. },
  58. {
  59. "UpstreamPathTemplate": "/signin-microsoft",
  60. "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put", "Head", "Options" ],
  61. "DownstreamPathTemplate": "/signin-microsoft",
  62. "DownstreamScheme": "https",
  63. "ServiceName": "ids4授权服务",
  64. "DangerousAcceptAnyServerCertificateValidator": true,
  65. "UpstreamHeaderTransform": {
  66. "X-Forwarded-For": "{RemoteIpAddress}"
  67. }
  68. },
  69. {
  70. "UpstreamPathTemplate": "/_vs/{url}",
  71. "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put", "Head", "Options" ],
  72. "DownstreamPathTemplate": "/_vs/{url}",
  73. "DownstreamScheme": "https",
  74. "ServiceName": "ids4授权服务",
  75. "DangerousAcceptAnyServerCertificateValidator": true,
  76. "UpstreamHeaderTransform": {
  77. "X-Forwarded-For": "{RemoteIpAddress}"
  78. }
  79. },
  80. {
  81. "DownstreamPathTemplate": "/IdentityCodeAuth/{url}",
  82. "DownstreamScheme": "https",
  83. "ServiceName": "ids4授权服务",
  84. "UpstreamPathTemplate": "/IdentityCodeAuth/{url}",
  85. "UpstreamHeaderTransform": {
  86. "X-Forwarded-For": "{RemoteIpAddress}"
  87. },
  88. "DangerousAcceptAnyServerCertificateValidator": true
  89. },
  90. {
  91. "DownstreamPathTemplate": "/signout-callback-oidc",
  92. "DownstreamScheme": "https",
  93. "ServiceName": "ids4授权服务",
  94. "UpstreamPathTemplate": "/signout-callback-oidc",
  95. "UpstreamHeaderTransform": {
  96. "X-Forwarded-For": "{RemoteIpAddress}"
  97. },
  98. "DangerousAcceptAnyServerCertificateValidator": true
  99. },
  100. {
  101. "DownstreamPathTemplate": "/{route}",
  102. "DownstreamScheme": "https",
  103. "ServiceName": "ids4授权服务",
  104. "UpstreamPathTemplate": "/{route}",
  105. "UpstreamHeaderTransform": {
  106. "X-Forwarded-For": "{RemoteIpAddress}"
  107. },
  108. "DangerousAcceptAnyServerCertificateValidator": true
  109. }
  110. ],
  111. "GlobalConfiguration": {
  112. "ServiceDiscoveryProvider": {
  113. "Namespace": "swprinter",
  114. "Type": "kube"
  115. }
  116. }
  117. }


然后就是API授权地址也需要改成外部域名地址的地址。

  1. "Identity_Config": {
  2. "Authority": "外部域名地址",
  3. "Audience": "ApiOne",
  4. "IsHttps": false,
  5. "ValidateIssuer": true,
  6. "ValidateAudience": false
  7. },


IdentityServer4修改授权地址为外部域名地址。

  1. "ExternalUrl": "外部域名地址"


Azure里面的应用注册里面,重定向的地址为:

  1. https://外部域名/ExBackLogoutUrl
  2. https://外部域名/signin-microsoft


注销的通道为:

  1. https://外部域名/signout-oidc


好了真正的好戏开始了。

502问题


当从微软那边登录成功之后,授权服务器跳转回SPA前端,当去授权服务器获取用户信息的时候报错502。
这是因为Cookie在数据包的头部太大了,Ingress nginx在转发的时候报错,所以我们需要去设置Ingress Nginx。
(我个人比较推荐使用IngressClass的方式进行解决,但目前项目赶集我后期会补上,这里我们就先用命令的方式来解决。)
首先找到Ingress nginx的pod,我这里是nginx-ingress-release-nginx-ingress-86b557d895-hgnlv,修改该nginx.conf与外部域名的配置文件。

  1. sudo mkdir /xx
  2. sudo chmod 777 /xx
  3. kubectl cp default/nginx-ingress-release-nginx-ingress-86b557d895-hgnlv:/etc/nginx/nginx.conf /xx/nginx.conf
  4. kubectl cp default/nginx-ingress-release-nginx-ingress-86b557d895-hgnlv:/etc/nginx/conf.d/外部域名.conf /xx/外部域名.conf
  1. # nginx.conf
  2. http{
  3. ...
  4. proxy_buffer_size 128k;
  5. proxy_buffers 4 256k;
  6. proxy_busy_buffers_size 256k;
  7. large_client_header_buffers 4 16k;
  8. ...
  9. }
  10. # 外部域名.conf
  11. location /{
  12. ...
  13. fastcgi_buffers 16 16k;
  14. fastcgi_buffer_size 32k;
  15. ...
  16. }
  1. # 复制修改后的配置到ingress nginx中
  2. kubectl cp /xx/nginx.conf default/nginx-ingress-release-nginx-ingress-86b557d895-hgnlv:/etc/nginx/nginx.conf
  3. kubectl cp /xx/外部域名.conf default/nginx-ingress-release-nginx-ingress-86b557d895-hgnlv:/etc/nginx/conf.d/外部域名.conf
  4. # 进入管道中
  5. kubectl exec -it pod/nginx-ingress-release-nginx-ingress-86b557d895-hgnlv bash
  6. # 重启nginx
  7. nginx -s reload

找不到外部域名而导致授权报错


就是你私有的服务器,在没有配置外部域名的情况下,直接在本机hosts上添加的地址和域名,但是由于内部服务器找不到该地址产生的500报错。
所以解决这个的方式第一种就是购买外部域名,并解析到你的ingress网关地址。
第二种方法,配置CoreDNS将外部域名解析为内部地址。

  1. kubectl edit cm/coredns -n kube-system
  1. apiVersion: v1
  2. data:
  3. Corefile: |
  4. .:53 {
  5. errors
  6. health {
  7. lameduck 5s
  8. }
  9. ready
  10. rewrite stop {
  11. name regex 外部域名地址 <网关服务>.<名称空间>.svc.cluster.local
  12. answer name <网关服务>.<名称空间>.svc.cluster.local 外部域名地址
  13. }
  14. kubernetes cluster.local in-addr.arpa ip6.arpa {
  15. pods insecure
  16. fallthrough in-addr.arpa ip6.arpa
  17. ttl 30
  18. }
  19. ...


然后等半分钟就解决了。


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

分布式服务架构微服务架构概念的区别联系

分布式:分散压力。微服务:分散能力。当下理解分布式:不同模块部署在不同服务器上作用:分布式解决网站高并发带来问题集...

jsController中分割字符串的方法

js: varstr=OpenRule; varstrs=newArray(); strs=str.split(&quot;,&quot;); for(vari=0;i&lt;strs.length;i++){ $(&q...

Service-stack.redis配置连接池读写分离(处理并发相关等)

配置连接池与读写分类 //写节点(主节点) List&lt;string&gt;writes=newList&lt;string&gt;(); writes.Add(&quot;123456a...

CSS相对定位绝对定位

一般相对定位和绝对定位可以配合起来使用 例如实现如下的效果 只需要在外层div设置为相对定位,在内部设置为绝对定位就...

C委托事件

1.什么是委托?  委托在C#里的意义和在现实里差不多,从字面意思理解即可。举个例子:领导委托小张去传递个文件,这就是...

asp.net core2.0 依赖注入 AddTransientAddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...

Vue.js+Layer实现表格数据绑定更新

一:使用Vue.js绑定好数据与更新事件 使用v-on绑定好事件,在事件里边直接把该行数据传递进去,在更新方法里边就可以直接...

下划线、换行、回车、空格ASCII码值对照表

下划线,ASCII码95换行 , ASCII码10回车 , ASCII码13空格 , ASCII码32ASCII码表:Bin(二进制)Oct(八进制)Dec(十进制)Hex(...

数据读取器指定的"xx"不兼容。某个类型为"xx"的成员在同名的数据读取器中没有对应的列

报错的地方var result= _db.Database.SqlQuery&lt;SMachine&gt;(sql).FirstOrDefault();经过分析,是因为SqlQuery方法查询...

git 下载提交命令

一.先使用git clone下载一个项目 git clone &#39;项目地址&#39; 这里要注意: clone的项目里边会自带git的一些信息,...

微信开发四 接受用户普通消息回复消息

微信接收用户普通消息的文章可以在官方中直接看微信普通消息分类:接受用户文本消息 与 回复文本信息 注意在接收用户普通...

记忆糖的关系【阅读听力】

Link Between Memory and SugarSugar On The BrainIt’s long been understood that there is a connection between memory...

婚姻心脏健康的关系【阅读听力】

Marriage and Heart HealthPlenty of studies have found that being married is generally good for health. One study ze...

iframe自适应高度配合net core使用

去掉iframe边框frameborder=&quot;0&quot;去掉滚动条scrolling=&quot;no&quot;iframe 自适应高度如果内容是固定的,那么就...

net core中使用url编码解码操作

net core中暂时还没有以前asp.net与mvc中的server对象。获取url的编码与解码操作不能使用以前的server对象来获取。使用的是...
这一世以无限游戏为使命!
排名
2
文章
657
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术