ASP.NET Core 奇技淫巧之接口代理转发
先讲讲本文的开发背景吧..
在如今前后端分离的大背景下,咱的客户又有要求啦~
要前后端分离~ 然因为种种原因..没办法用用纯前端的框架(其实是学习成本高,又没钱请前端开发人员)...
所以最终决定了一种方案..
那就是采用MVC(只处理前端视图层,单纯是为了托管在.net core上)+Webapi的方式来实现前后端分离(讲真,很奇葩)..
那么问题就随之而来了.
现在主流的前端框架都是托管在nodejs上,是通过axios来访问后端API,可以通过配置axios的代理配置(proxyTable)来实现跨域访问.
那么我们的JS运行在MVC上,托管在.net core上..那咋办呢?..没有现成的转发轮子..我们只有自己造了..
所以这就是本篇的背景 - -.~
幸运的是ASP.NET Core 给我们提供了强大的中间件模式.
我们完全可以通过定义一个转发中间件的形式来实现代理接口转发,流程如图:

废话不多说,我们来创建我们的中间件:
一.创建检测约定URL的接口与实现
首先定义一个接口IUrlRewriter 用来检测我们的URL是否有对应前缀,如果有,则产生新的URL地址:
这里我们定义接口是为了方便以后更好的更换注入类来实现快速更换检测前缀的规则.
public interface IUrlRewriter
{
Task<Uri> RewriteUri(HttpContext context);
}
实现这个接口,如下(解释都在注释里了):
public class PrefixRewriter : IUrlRewriter
{
private readonly PathString _prefix; //前缀值
private readonly string _newHost; //转发的地址 public PrefixRewriter(PathString prefix, string newHost)
{
_prefix = prefix;
_newHost = newHost;
} public Task<Uri> RewriteUri(HttpContext context)
{
if (context.Request.Path.StartsWithSegments(_prefix))//判断访问是否含有前缀
{
var newUri = context.Request.Path.Value.Remove(, _prefix.Value.Length) + context.Request.QueryString;
var targetUri = new Uri(_newHost + newUri);
return Task.FromResult(targetUri);
} return Task.FromResult((Uri)null);
}
}
二.创建代理转发需要的ProxyHttpClient
创建独立的ProxyHttpClient,主要是为了区分代理转发的httpClient,方便后期添加日志或做别的处理.代码如下:
public class ProxyHttpClient
{
public HttpClient Client { get; private set; } public ProxyHttpClient(HttpClient httpClient)
{
Client = httpClient;
}
}
三.创建代理转发的中间件
代码如下,中间件嘛,主要就是Invoke方法了,说明可以看注释.
public class ProxyMiddleware
{
private ProxyHttpClient _proxyHttpClient;
private const string CDN_HEADER_NAME = "Cache-Control";
private static readonly string[] NotForwardedHttpHeaders = new[] { "Connection", "Host" };
private readonly RequestDelegate _next;
private readonly ILogger<ProxyMiddleware> _logger; public ProxyMiddleware(
RequestDelegate next,
ILogger<ProxyMiddleware> logger,
ProxyHttpClient proxyHttpClient
)
{
_next = next;
_logger = logger;
_proxyHttpClient = proxyHttpClient;
} /// <summary>
/// 通过中间件,拦截访问,检测前缀,并转发
/// </summary>
/// <param name="context"></param>
/// <param name="urlRewriter"></param>
/// <returns></returns>
public async Task Invoke(HttpContext context, IUrlRewriter urlRewriter) {
var targetUri = await urlRewriter.RewriteUri(context); if (targetUri != null)
{
var requestMessage = GenerateProxifiedRequest(context, targetUri);
await SendAsync(context, requestMessage); return;
} await _next(context);
} private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage)
{ using (var responseMessage = await _proxyHttpClient.Client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
{
context.Response.StatusCode = (int)responseMessage.StatusCode; foreach (var header in responseMessage.Headers)
{
context.Response.Headers[header.Key] = header.Value.ToArray();
} foreach (var header in responseMessage.Content.Headers)
{
context.Response.Headers[header.Key] = header.Value.ToArray();
} context.Response.Headers.Remove("transfer-encoding"); if (!context.Response.Headers.ContainsKey(CDN_HEADER_NAME))
{
context.Response.Headers.Add(CDN_HEADER_NAME, "no-cache, no-store");
} await responseMessage.Content.CopyToAsync(context.Response.Body);
}
} private static HttpRequestMessage GenerateProxifiedRequest(HttpContext context, Uri targetUri)
{
var requestMessage = new HttpRequestMessage();
CopyRequestContentAndHeaders(context, requestMessage);
requestMessage.RequestUri = targetUri;
requestMessage.Headers.Host = targetUri.Host;
requestMessage.Method = GetMethod(context.Request.Method);
return requestMessage;
} private static void CopyRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage)
{
var requestMethod = context.Request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(context.Request.Body);
requestMessage.Content = streamContent;
} foreach (var header in context.Request.Headers)
{
if (!NotForwardedHttpHeaders.Contains(header.Key))
{
if (header.Key != "User-Agent")
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
else
{
string userAgent = header.Value.Count > ? (header.Value[] + " " + context.TraceIdentifier) : string.Empty; if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, userAgent) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, userAgent);
}
} }
}
} private static HttpMethod GetMethod(string method)
{
if (HttpMethods.IsDelete(method)) return HttpMethod.Delete;
if (HttpMethods.IsGet(method)) return HttpMethod.Get;
if (HttpMethods.IsHead(method)) return HttpMethod.Head;
if (HttpMethods.IsOptions(method)) return HttpMethod.Options;
if (HttpMethods.IsPost(method)) return HttpMethod.Post;
if (HttpMethods.IsPut(method)) return HttpMethod.Put;
if (HttpMethods.IsTrace(method)) return HttpMethod.Trace;
return new HttpMethod(method);
}
}
四.注入和启用我们的中间件和ProxyHttpClient
我们在Startup的ConfigureServices中添加如下代码,注入我们的HttpClient与IUrlRewriter,如下:
services.AddHttpClient<ProxyHttpClient>()
.ConfigurePrimaryHttpMessageHandler(x => new HttpClientHandler()
{
AllowAutoRedirect = false,
MaxConnectionsPerServer = int.MaxValue,
UseCookies = false,
}); //注入我们定义的HttpClient
services.AddSingleton<IUrlRewriter>(new PrefixRewriter("/webapp", "http://localhost:63445"));//这里填写前缀与需要转发的地址
然后在Startup的Configure中,启动我们的中间件,如下:
app.UseMiddleware<ProxyMiddleware>();
五.测试中间件效果
我们编写前端代码如下:
created: function () {
this.mockTableData1();
axios.get("/webapp/api/values/get", "").then(res => { alert(res.data[]) });
axios.post("/webapp/api/values/post",{value: 'david'}).then(res => { alert(res.data.message) });
}
在另外的WebApi项目,编写接口如下:
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", accstring.ToString() };
} [HttpPost]
public AjaxResult Post(dynamic value)
{
string aaa = JsonConvert.SerializeObject(value);
return Success("OK");
}
效果如下,可以看到我们的视图正确的获取到了返回值:

这里我们通过中间件的形式实现了接口的代理转发,在具体的使用过程中肯定还会有一些小问题,而且这里我们只实现了Http的转发.ws的则没有.
如果要使用的话,其实国外有一个开源的项目:https://github.com/ProxyKit, 已经有900多个star了.应该还不错.
ASP.NET Core 奇技淫巧之接口代理转发的更多相关文章
- 部署Asp.net core & Nginx,通过nginx转发
部署Asp.net core & Nginx,通过nginx转发 CentOS 7 x64 1.vs2017 建立Asp.net core项目,并发布到目录 2.通过FTP工具,将程序copy ...
- Asp.net Core的Swagger接口根据模块、版本分组
近期一直在学习Asp.net Core,微软的文档太难看,都是英文翻译过来的,很不友好,感谢这个博客,从壹开始前后端分离[ .NET Core2.0 +Vue2.0 ],让我入门了,刚学到这个Swag ...
- 发布自己的第一版asp.net core的RESTful接口程序
使用window开发一个简单的asp.net Core的RESTfull程序,网上很多,这里不说,我是直接使用IDE自己生成的项目来发布的.没有修改过主要代码.在IDE里发布到本地目录,得到类似文件 ...
- Angular调用Asp.net Core JWT Authentication接口
基本思路是调用登录接口,获取token,使用token请求其他JWT接口: getHomeDetails(): Observable<HomeDetails> { let headers ...
- ASP.NET Core Swagger 显示接口注释
在Startup中 services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info { Title ...
- Asp.net Core 项目API接口服务器部署
Windows server 2008服务器部署: DotNetCore.1.0.0.RC2-WindowsHosting 或者DotNetCore.1.0.5_1.1.2-WindowsHostin ...
- ASP.NET Core WebApi构建API接口服务实战演练
一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...
- ASP.NET Core 2.2 和之前版本区别: 可以在IIS上进行ASP.NET核心进程托管 (翻译)
原文链接: https://weblog.west-wind.com/posts/2019/Mar/16/ASPNET-Core-Hosting-on-IIS-with-ASPNET-Core-22 ...
- Jexus针对Asp.net core应用程序的六大不可替代的优势
1,配置简便:在Jexus上,Asp.net core只是Jexus上的一个"站点",因此,只需在Jexus上配置这个站点就行,无需其它配置: 2,操作统一:Jexus停止这个站点 ...
随机推荐
- eclipse的使用小技能
eclipse的使用小技能 文章来源:http://blog.csdn.net/ljfbest/article/details/7465003 关于eclipse的使用方面,其实有些东西都是小技巧的东 ...
- java 之 实例方法和类方法
类方法:使用static修饰(静态方法),属于整个类的,不是属于某个实例的,只能处理static域或调用static方法: 实例方法:属于对象的方法,由对象来调用. 判断类方法,类方法的前面有stat ...
- 通过PHP工具箱-站点域名管理(创建本地虚拟主机)
工具:php程序员工具箱(网上很多请自己搜索下载) 1.点击其它选项菜单 -> 选择站点域名管理.如下图 2.进入站点域名管理.如下图(初始的时候,站点为空) 3.设置站点管理.如下图 网站域名 ...
- Springboot启动扩展点超详细总结,再也不怕面试官问了
1.背景 Spring的核心思想就是容器,当容器refresh的时候,外部看上去风平浪静,其实内部则是一片惊涛骇浪,汪洋一片.Springboot更是封装了Spring,遵循约定大于配置,加上自动装配 ...
- 题解 CF613D 【Kingdom and its Cities】
考虑树形\(DP\),设\(num_x\)记录的为当\(1\)为根时,以\(x\)为子树中重要城市的个数. 那么进行分类讨论: ① 当\(num_x≠0\)时,则需将其所有满足\(num_y≠0\)的 ...
- MVC + EFCore 项目实战 - 数仓管理系统7 - 数据源管理中--新增数据源
上篇我们完成了数据源列表展示功能(还未测试). 本篇我们来新增数据源,并查看列表展示功能. 接上篇: 二.数据源管理功能开发 2.新增数据源 我们用模态对话框来完成数据源的新增,效果如下图: 我们 ...
- 01 shell编程之变量定义
一.SHELL介绍 ㈠ 什么是shell脚本? 简单来说就是将需要执行的命令保存到文本中,按照顺序执行.它是解释型的,意味着不需要编译. 若干命令 + 脚本的基本格式 + 脚本特定语法 + 思想= s ...
- DICOM 相关概念了解
前言: 正如前述文章中提到的,DICOM(Digitial Image Communications in Medicine)是所有从事医学影像处理的工作者需要了解的最基本的图像格式. 假 ...
- Kafka入门(2):消费与位移
摘要 在这篇文章中,我将从消息在Kafka中的物理存储方式讲起,介绍分区-日志段-日志的各个层次. 然后我将接着上一篇文章的内容,把消费者的内容展开讲一讲,区分消费者与消费者组,以及这么设计有什么用. ...
- Python列表脚本操作符
Python列表脚本操作符: len(列表名): 查看列表长度 # 使用 len(列表名) 方法查看列表长度 lst = [1,2,3,4] print(len(lst)) # # 注:嵌套列表算一个 ...