本文3.0版本文章

 
 

更新反馈

1、博友@落幕残情童鞋说到了,Nginx反向代理实现跨域,因为我目前还没有使用到,给忽略了,这次记录下,为下次补充。此坑已填

2、提示:跨域的姊妹篇——《三十三║ ⅖ 种方法实现完美跨域

代码已上传Github+Gitee,文末有地址

  今天忙着给小伙伴们提出的问题解答,时间上没把握好,都快下班了,赶紧发布:书说上文《从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十一 || AOP自定义筛选,Redis入门 11.1》,昨天咱们说到了分布式缓存键值数据库,主要讲解了如何安装,使用,最后遗留了一个问题,同步+Redis缓存还是比较简单,如何使用异步泛型存取Redis,还是一直我的心结,希望大家有会的,可以不吝赐教,本系列教程已经基本到了尾声,今天就说两个小的知识点,既然本系列是讲解前后端分离的,那一定会遇到跨域的问题,没错,今天将说下跨域!然后顺便说一下DTOs(数据传输对象),这些东西大家都用过,比如,在MVC中定义一个ViewModel,是基于Model实体类的,然后做了相应的变化,以适应前端需求,没错,就是这个,如果大型的实体类,一个个复杂的话会稍显费力,今天就是用一个自动映射工具——AutoMapper。

零、今天完成左下角的深紫色部分

一、为什么会出现跨域的问题

跨域问题由来已久,主要是来源于浏览器的”同源策略”。 
​ 何为同源?只有当协议、端口、和域名都相同的页面,则两个页面具有相同的源。只要网站的 协议名protocol、 主机host、 端口号port 这三个中的任意一个不同,网站间的数据请求与传输便构成了跨域调用,会受到同源策略的限制。 ​ 同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。浏览器的同源策略,出于防范跨站脚本的攻击,禁止客户端脚本(如 JavaScript)对不同域的服务进行跨站调用(通常指使用XMLHttpRequest请求)。

所以说我们在web中,我们无法去获取跨域的请求,常见的就是无法通过js获取接口(这里要说下我的以前使用的经验:在同源系统下,前端js去调用后端接口,然后后端C#去调取跨域接口,这是我以前采用的办法,但是前后端分离,这个办法肯定就是不行了,因为那时候已经没有了前后端之分,是两个项目),所以我们只要合理使用同源策略,就可以达到跨域访问的目的。

二、三种跨域方式 之JsonP

我自己建立了一个静态页面,用来模拟前端访问,具体如下步骤:

1、模拟前端访问页面

新建一个Html页面,使用Jquery来发送请求(文件在项目的WWW文件夹下,大家可以自己下载,或者Copy下边代码)。

一共三种跨域方法:

<html>
<head>
<meta charset="utf-8">
<title>Blog.Core</title>
<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
<style>
div {
margin: 10px;
word-wrap: break-word;
}
</style>
<script> $(document).ready(function () {
$("#jsonp").click(function () { $.getJSON("/api/Login/jsonp?callBack=?", function (data) {
$("#data-jsonp").html("数据: " + data.value);
});
}); $("#cors").click(function () {
$.get("/api/Login/Token", function (data, status) {
console.log(data);
$("#status-cors").html("状态: " + status);
$("#data-cors").html("数据: " + data? data.token:"失败");
});
}); $("#cors-post").click(function () {
let postdata = {
"bID": ,
"bsubmitter": "",
"btitle": "",
"bcategory": "",
"bcontent": "",
"btraffic": ,
"bcommentNum": ,
"bUpdateTime": "2018-11-08T02:36:26.557Z",
"bCreateTime": "2018-11-08T02:36:26.557Z",
"bRemark": "string"
};
$.ajax({
type: 'post',
url: '/api/Values',
contentType: 'application/json',
data: JSON.stringify(postdata),
success: function (data, status) {
console.log(data);
$("#status-cors-post").html("状态: " + status);
$("#data-cors-post").html("数据: " + JSON.stringify(data));
}
}); //$.ajax({
// type: "POST",
// url: "/api/Values",
// success: function (data, status) {
// console.log(data);
// $("#status-cors-post").html("状态: " + status);
// $("#data-cors-post").html("数据: " + data);
// }
//}); }); });
</script>
</head>
<body> <h3>通过JsonP实现跨域请求</h3>
<button id="jsonp">发送一个 GET </button> <div id="status-jsonp"></div>
<div id="data-jsonp"></div>
<hr /> <h3>添加请求头实现跨域</h3>

<hr /> <h3>通过CORS实现跨域请求,另需要在服务器端配置CORE</h3>
<button id="cors">发送一个 GET </button> <div id="status-cors"></div>
<div id="data-cors"></div>
<hr />
<button id="cors-post">发送一个 POST </button> <div id="status-cors-post"></div>
<div id="data-cors-post"></div>
<hr />
</body>
</html>

注意:这里一定要注意jsonp的前端页面请求写法,要求很严谨

2、请求页面部署

1、其实只需要当前Blog.Core 项目配置了静态文件中间件,直接访问就可以

比如我的在线地址:查看右侧公告栏

2、单独部署:将这个页面部署到自己的IIS中(拷贝到文件里,直接在iis添加该文件,访问刚刚的Html文件目录就行)

3、设计后台接口

在我们的项目 LoginController 中,设计Jsonp接口,Core调用的接口我们已经有了,就是之前获取Token的接口GetJWTStr

      [HttpGet]
[Route("jsonp")]
public void Getjsonp(string callBack, long id = , string sub = "Admin", int expiresSliding = , int expiresAbsoulute = )
{
TokenModel tokenModel = new TokenModel();
tokenModel.Uid = id;
tokenModel.Sub = sub; DateTime d1 = DateTime.Now;
DateTime d2 = d1.AddMinutes(expiresSliding);
DateTime d3 = d1.AddDays(expiresAbsoulute);
TimeSpan sliding = d2 - d1;
TimeSpan absoulute = d3 - d1; string jwtStr = BlogCoreToken.IssueJWT(tokenModel, sliding, absoulute);

       //重要,一定要这么写
string response = string.Format("\"value\":\"{0}\"", jwtStr);
string call = callBack + "({"+response+"})";
Response.WriteAsync(call);
}

注意:这里一定要注意jsonp的接口写法,要求很严谨

4、点击”通过JsonP实现跨域请求“按钮,发现已经有数据了,证明Jsonp跨域已经成功,你可以换成自己的域名试一试,但是Cors的还不行

三、三种跨域方式 之添加请求头实现跨域

这里我没有写到代码里,是在一般处理程序里之前用到的

1、后端

        public void ProcessRequest(HttpContext context)
{
//接收参数
string uName = context.Request["name"];
string data = "{\"name\":\"" + uName + "\",\"age\":\"18\"}";
//只需在服务端添加以下两句
context.Response.AddHeader("Access-Control-Allow-Origin", "*");
//跨域可以请求的方式
context.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET");
context.Response.Write(data);
}

2、前端

       function ashxRequest() {
$.post("http://localhost:5551/ashxRequest.ashx", { name: "halo" }, function (data) {
for (var i in data) {
alert(data[i]);
}
}, "json")
}

大家感兴趣可以自己实验下。有问题请留言

四、三种跨域方式 之 高效CORS

1、前端ajax调用

前端的代码在jsonp的时候已经写好,请往上看第二节,后端接口也是Token接口

剩下的就是配置跨域了,很简单!

2、配置 CORS 跨域

在ConfigureServices中添加

            #region CORS
//跨域第一种方法,先注入服务,声明策略,然后再下边app中配置开启中间件
services.AddCors(c =>
{
//↓↓↓↓↓↓↓注意正式环境不要使用这种全开放的处理↓↓↓↓↓↓↓↓↓↓
c.AddPolicy("AllRequests", policy =>
{
policy
.AllowAnyOrigin()//允许任何源
.AllowAnyMethod()//允许任何方式
.AllowAnyHeader()//允许任何头
.AllowCredentials();//允许cookie
});
//↑↑↑↑↑↑↑注意正式环境不要使用这种全开放的处理↑↑↑↑↑↑↑↑↑↑ //一般采用这种方法
c.AddPolicy("LimitRequests", policy =>
{
policy
.WithOrigins("http://127.0.0.1:1818", "http://localhost:8080", "http://localhost:8021", "http://localhost:8081", "http://localhost:1818")//支持多个域名端口,注意端口号后不要带/斜杆:比如localhost:8000/,是错的
.AllowAnyHeader()//Ensures that the policy allows any header.
.AllowAnyMethod();
});
}); // 这是第二种注入跨域服务的方法,这里有歧义,部分读者可能没看懂,请看下边解释
//services.AddCors();
#endregion

歧义解释:

可能有些读者会说,你这里写错了,应该是 app.UseCors() ,我肯定是知道的,那为啥还要这么写呢,是因为这里我提供了两套 Cors 跨域写法:

1、配置在 configureServices 中,然后再在管道中开启中间件,就是上边的写法;

2、还有一个是,只在 configureServices 中,开启服务,然后在中间件中,具体的配置:

 //跨域第一种版本,请要ConfigureService中配置服务 services.AddCors();
// app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader()
//.AllowAnyMethod());

基本注释都有,大家都能看的懂,就这么简单!

注意:在定义策略 LimitRequests 的时候,源域名应该是客户端请求的端口域名,不是当前API的域名端口。

感谢博友 @学弱 提醒:CORS的配置一定要放在AutoFac前面,否则builder.Populate(services);后,你再进行配置会没有效果。

3、启动中间件

在启动文件 的Configure 配置方法里,添加启用Cors中间件服务

感谢博友@kiritio_ooo的提醒,Git已更新

注意:如果你使用了 app.UserMvc() 或者 app.UseHttpsRedirection()这类的中间件,一定要把 app.UseCors() 写在它们的上边,先进行跨域,再进行 Http 请求,否则会提示跨域失败。

因为这两个都是涉及到 Http请求的,如果你不跨域就直接转发或者mvc,那肯定报错。

4、运行调试,一切正常

至此,跨域的问题已经完成辣

重要:如果你想查看效果,我的最新的Github上代码已经给大家写好了,大家clone以后,只需要执行 http://localhost:8081/corspost.html ,就能看到各种效果了。当然如果懒得下载,可以看我的在线效果:查看右侧公告栏


注意:这里要说下,如果遇到了跨域失败的提示,比如这样:

这个并不一定是没有配置好导致的跨域失败,还有可能是接口有错误,比如 500,或者是 404 了,导致的接口异常,所以就提示访问有错误。

五、其他跨域方法补充

请参考我的文章:

三十三║ ⅖ 种方法实现完美跨域

  nginx是一个高性能的web服务器,常用作反向代理服务器。nginx作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上。

通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。

对于浏览器来说,访问的就是同源服务器上的一个url。而nginx通过检测url前缀,把http请求转发到后面真实的物理服务器。并通过rewrite命令把前缀再去掉。这样真实的服务器就可以正确处理请求,并且并不知道这个请求是来自代理服务器的。

简单说,nginx服务器欺骗了浏览器,让它认为这是同源调用,从而解决了浏览器的跨域问题。又通过重写url,欺骗了真实的服务器,让它以为这个http请求是直接来自与用户浏览器的。

这样,为了解决跨域问题,只需要动一下nginx配置文件即可。

六、结语

三种办法其实都能达到目的,但是优缺点也很明显

1、手动创建JSONP跨域

优点:无浏览器要求,可以在任何浏览器中使用此方式

缺点:格式要求很严格,只支持get请求方式,请求的后端出错不会有提示,造成不能处理异常

2、添加请求头实现跨域

优点:支持任意请求方式,并且后端出错会像非跨域那样有报错,可以对异常进行处理

缺点:兼容性不是很好,IE的话 <IE10 都不支持此方式

虽然CORS的方法有点儿类似请求头,但是封装,兼容性,灵活性都要好的很多,强烈推荐。

七、初探DTOs

请看以下实体类

//数据库实体类
public class Author
{
public string Name { get; set; }
}
public class Book
{
public string Title { get; set; }
public Author Author { get; set; }
}
//页面实体类
public class BookViewModel
{
public string Title { get; set; }
public string Author { get; set; }
}
//api调用
BookViewModel model = new BookViewModel
{
Title = book.Title,
Author = book.Author.Name
}

  上面的例子相当的直观了,我们平时也是这么用的基本,但是问题也随之而来了,我们可以看到在上面的代码中,如果一旦在Book对象里添加了一个额外的字段,而后想在前台页面输出这个字段,那么就需要去在项目里找到每一处有这样BookViewModel转换字段的地方,这是非常繁琐的。另外,BookViewModel.Author是一个string类型的字段,但是Book.Author属性却是Author对象类型的,我们用的解决方法是通过Book.Auther对象来取得Author的Name属性值,然后再赋值给BookViewModel的Author属性,这样看起行的通,但是想一想,如果打算在以后的开发中把Name拆分成两个-FisrtName和LastName,我的天呐!我们得去把原来的ViewModel对象也拆分成对应的两个字段,然后在项目中找到所有的转换,然后替换。 
那么有什么办法或者工具来帮助我们能够避免这样的情况发生呢?AutoMapper正是符合要求的一款插件。

只需一键操作,就能一劳永逸,解决所有问题,然后通过依赖注入,快速使用:

        //AutoMapper自动映射
//Mapper.Initialize(cfg => cfg.CreateMap<BlogArticle, BlogViewModels>());
//BlogViewModels models = Mapper.Map<BlogArticle, BlogViewModels>(blogArticle); BlogViewModels models = IMapper.Map<BlogViewModels>(blogArticle);//就这一句话完全搞定所有转换

今天因为时间的关系,没有说到Automapper,明天再见吧~

八、CODE

https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core

从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十二 || 三种跨域方式比较,DTOs(数据传输对象)初探的更多相关文章

  1. 前后端分离 导致的 静态页面 加载 <script type="module" > 报CORS 跨域错误,提示 blocked by CORS policy

    1.前言 静态页面 加载 <script type="module" > 报CORS 跨域错误,提示Access to script at ftp:///xxx.js ...

  2. 从壹开始前后端分离 [.netCore 填坑 ] 三十三║ ⅖ 种方法实现完美跨域

    缘起 哈喽大家周四好,趁着大家在团建的时候花一个下午学点儿东西,也是督促大家学习哟,希望大家看到老张的文章,可以有一丢丢的学习动力.不过话说过来,该吃的团建还是要去的,不能学我呀 [ /(ㄒoㄒ)/~ ...

  3. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十三 || DTOs 对象映射使用,项目部署Windows+Linux完整版

    更新 很多小伙伴在用 IIS 发布的时候,总是会有一些问题,文章下边 #autoid-6-0-0 我也简单的动图展示了,如何 publish 到 IIS 的过程,如果你能看懂,却发现自己的项目有问题的 ...

  4. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十七 ║Vue基础:使用Vue.js 来画博客首页+指令(一)

    缘起 书说前两篇文章<十五 ║ Vue前篇:JS对象&字面量&this>和 <十六 ║ Vue前篇:ES6初体验 & 模块化编程>,已经通过对js面向对 ...

  5. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十九║Vue基础: 样式动态绑定+生命周期

    回顾 哈喽大家好,前后端分离系列文章又开始了,今天周一,还是感谢大家花时间来观看我写的博客,周末呢,没有写文章,但是也没有闲着,主要是研究了下遗留问题,看过之前文章的应该知道,之前的在AOP使用Red ...

  6. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之七 || API项目整体搭建 6.2 轻量级ORM

    更新 1.在使用的时候,特别是更新数据的时候,如果不知道哪里有问题,可以查看数据库 和 实体类 的字段,是否大小写一致,比如 name 和 Name 2.在使用Sqlsugar 的 CodeFirst ...

  7. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存

    代码已上传Github+Gitee,文末有地址 上回<从壹开始前后端分离[ .NET Core2.0 Api + Vue 2.0 + AOP + 分布式]框架之九 || 依赖注入IoC学习 + ...

  8. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十一 || AOP自定义筛选,Redis入门 11.1

    代码已上传Github+Gitee,文末有地址 书说上文<从壹开始前后端分离[ .NET Core2.0 Api + Vue 2.0 + AOP + 分布式]框架之十 || AOP面向切面编程浅 ...

  9. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十五 ║Vue基础:JS面向对象&字面量& this字

    缘起 书接上文<从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十四 ║ VUE 计划书 & 我的前后端开发简史>,昨天咱们说到了以我的经历说明的web开发经历的 ...

随机推荐

  1. ES6-LET,变量提升,函数提升

    1:let命令 ①类似var,但只在let所在代码块内有效 ②不存在变量提升 ③暂时性死区(TDZ)—有let命令时,在此命令前都没法使用此变量 ④不允许重复声明 ⑤ES6允许块级作用域任意嵌套 ⑥E ...

  2. RabbitMQ (四) 路由选择 (Routing)

    上一篇博客我们建立了一个简单的日志系统,我们能够广播日志消息给所有你的接收者,如果你不了解,请查看:RabbitMQ (三) 发布/订阅.本篇博客我们准备给日志系统添加新的特性,让日志接收者能够订阅部 ...

  3. 【线段树】Bzoj1230 [Usaco2008 Nov]lites 开关灯

    Description Farmer John尝试通过和奶牛们玩益智玩具来保持他的奶牛们思维敏捷. 其中一个大型玩具是牛栏中的灯. N (2 <= N <= 100,000) 头奶牛中的每 ...

  4. python-----HTMLTestRunner报告生成注意点!

    简单的测试加HTMLTestRunner使用的具体方式如下:

  5. linux 挂载共享文件夹

    1.背景 通常会有这样的场景,开发人员在Windows编写代码,然后放在linux环境编译,我们通过mount命令就可以实现将代码直接挂到linux环境上去,使Windows上的共享文件夹就像linu ...

  6. C#-Xamarin的Android项目开发(二)——控件应用

    相信我,这不是一篇吐槽文章.... 基础控件 Android的控件和控件样式非常特别,它是一种内联特别高的设计模式,换句话说,它是非常烂的设计.... 但在这种特别的关系里还是有一定的规律的,下面我们 ...

  7. 死磕 java集合之ConcurrentSkipListSet源码分析——Set大汇总

    问题 (1)ConcurrentSkipListSet的底层是ConcurrentSkipListMap吗? (2)ConcurrentSkipListSet是线程安全的吗? (3)Concurren ...

  8. .NET Core IdentityServer4实战 第三章-使用EntityFramework Core进行持久化配置

    内容:本文带大家使用IdentityServer4进行使用使用EntityFramework Core进行配置和操作数据 作者:zara(张子浩) 欢迎分享,但需在文章鲜明处留下原文地址. 前两章内容 ...

  9. 『Möbius函数与Möbius反演』

    Möbius函数 定义 设正整数\(n\)算数基本定理分解后为\(n=\prod_{i=1}^{k}p_i^{a_i}\),定义函数 \[ \mu(n)= \begin{cases} 0\ \ (\e ...

  10. Spring boot 继承 阿里 autoconfig 配置环境参数

    前提:基于springboot 项目 1. 配置pom.xml 文件 <plugin> <groupId>com.alibaba.citrus.tool</groupId ...