一张图搞定OAuth2.0 在Office应用中打开WPF窗体而且让子窗体显示在Office应用上 完全关闭Excle进程的几个方法 (七)Net Core项目使用Controller之二

一张图搞定OAuth2.0

 

一、引言

本篇文章是介绍OAuth2.0中最经典最经常使用的一种受权模式:受权码模式jquery

很是简单的一件事情,网上一堆神乎其神的讲解,让我不得不写一篇文章来终结它们。git

一项新的技术,无非就是了解它是什么为何怎么用。至于为何,本篇文章不作重点探讨,网上会有各类文章举各类什么丢钥匙、发船票的例子供你去阅读,我的认为仍是有些哗众取宠,没有聊到本质。程序员

那咱们就重点聊聊OAuth2.0是什么怎么用。但首先在读本文以前,你要先对OAuth2.0有必定的了解,建议先读一下阮一峰的oauth2.0文章,直接看“受权码模式”便可,带着疑问再来读本文效果更好。github

http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html数据库

二、OAuth2.0是什么

OAuth2.0是什么——豆瓣和QQ的故事

OAuth简单说就是一种受权的协议,只要受权方和被受权方遵照这个协议去写代码提供服务,那双方就是实现了OAuth模式。express

举个例子,你想登陆豆瓣去看看电影评论,但你丫的历来没注册过豆瓣帐号,又不想新注册一个再使用豆瓣,怎么办呢?不用担忧,豆瓣已经为你这种懒人作了准备,用你的qq号能够受权给豆瓣进行登陆,请看。api

第一步:在豆瓣官网点击用qq登陆浏览器

第二步:跳转到qq登陆页面输入用户名密码,而后点受权并登陆服务器

 

第三步:跳回到豆瓣页面,成功登陆

 这几秒钟以内发生的事情,在无知的用户视角看来,就是在豆瓣官网上输了个qq号和密码就登陆成功了。在一些细心的用户视角看来,页面经历了从豆瓣到qq,再从qq到豆瓣的两次页面跳转。但做为一群专业的程序员,咱们还应该从上帝视角来看这个过程。

OAuth2.0是什么——上帝视角

  简单来讲,上述例子中的豆瓣就是客户端,QQ就是认证服务器,OAuth2.0就是客户端和认证服务器之间因为相互不信任而产生的一个受权协议。呵呵,要是相互信任那QQ直接把本身数据库给豆瓣好了,你直接在豆瓣输入qq帐号密码查下数据库验证就登录呗,还跳来跳去的多麻烦。

  先上一张图,该图描绘了只几秒钟发生的全部事情用上帝视角来看的流程

 就这这张图,来讲一下上述例子中的三个步骤在图中的表现。所用到的请求路径名称都是虚构的,所附带的请求参数忽略了一些非重点的。

如想了解每次的请求和响应的标准齐全的参数,仍是去读那篇阮一峰的文章。http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

第一步:在豆瓣官网点击用qq登陆

  当你点击用qq登陆的小图标时,其实是向豆瓣的服务器发起了一个 http://www.douban.com/leadToAuthorize 的请求,豆瓣服务器会响应一个重定向地址,指向qq受权登陆

  浏览器接到重定向地址 http://www.qq.com/authorize?callback=www.douban.com/callback ,再次访问。并注意到此次访问带了一个参数是callback,以便qq那边受权成功再次让浏览器发起这个callback请求。否则qq怎么知道你让我受权后要返回那个页面啊,天天让我受权的像豆瓣这样的网站这么多。

  至于访问这个地址以后,qq那边作出怎样的回应,就是第二步的事情了。总之第一步即对应了图中的这些部分。

第二步:跳转到qq登陆页面输入用户名密码,而后点受权并登陆

  上一步中浏览器接到重定向地址并访问 http://www.qq.com/authorize?callback=www.douban.com/callback

  qq的服务器接受到了豆瓣访问的authorize,在次例中所给出的回应是跳转到qq的登陆页面,用户输入帐号密码点击受权并登陆按钮后,必定还会访问qq服务器中校验用户名密码的方法,若校验成功,该方法会响应浏览器一个重定向地址,并附上一个code(受权码)。因为豆瓣只关心像qq发起authorize请求后会返回一个code,并不关心qq是如何校验用户的,而且这个过程每一个受权服务器可能会作些个性化的处理,只要最终的结果是返回给浏览器一个重定向并附上code便可,因此这个过程在图中并无详细展开。现把展开图画给你们。

第三步:跳回到豆瓣页面,成功登陆

 这一步背后的过程实际上是最繁琐的,但对于用户来讲是彻底感知不到的。用户在QQ登陆页面点击受权登录后,就直接跳转到豆瓣首页了,但其实经历了不少隐藏的过程。

首先接上一步,QQ服务器在判断登陆成功后,使页面重定向到以前豆瓣发来的callback并附上code受权码,即 callback=www.douban.com/callback 

页面接到重定向,发起 http://www.douban.com/callback 请求

豆瓣服务器收到请求后,作了两件再次与QQ沟通的事,即模拟浏览器发起了两次请求。一个是用拿到的code去换token,另外一个就是用拿到的token换取用户信息。最后将用户信息储存起来,返回给浏览器其首页的视图。到此OAuth2.0受权结束。

 

三、OAuth2.0怎么写

了解了上述过程后,代码天然就不难写了,起码框架是能够写出来的。我在github上分享了一个我本身模拟的简单的不能再简单的oauth2.0,你们能够参考一下,仅仅用于了解oauth的过程,可别用于公司哦,否则老板得开除你。

github地址:https://github.com/sunym1993/dataU-OAuth.git/

若是没法下载,能够加我单独发。

项目结构很是简单,只有两个模块,分别是豆瓣和QQ,分别启动便可。

最终效果也很是简单清晰,下面请忍受low逼的显示效果

第一步

第二步

第三步

 

 
QQ受权服务器产生的token会被豆瓣写入到用户浏览器的cookie里面吗?
若是写入的话,那么该cookie的域是豆瓣仍是QQ受权服务器的?
若是写入的话,那么是在那一步写入的,是最后一步吗?
麻烦给解释下以上三个问题。
您的问题已经超出了oauth自己,oauth只是一个协议,最终传回用户数据时协议的全过程已经结束,它的任务已经完成。至于服务怎么用这些数据,是存入cookie仍是什么的,那不一样的服务方会有不一样的选择,因人而异了。
固然若是非要描述的话,若是豆瓣选择存入cookie,那域固然是豆瓣的域了,此时已与QQ无关。
我上传到码云了  https://gitee.com/sunym1993/datauoauthqq_bean.git
 

理解OAuth 2.0

 

 

 

在Office应用中打开WPF窗体而且让子窗体显示在Office应用上

在.NET主程序中,咱们能够经过建立 ExcelApplication 对象来打开一个Excel应用程序,若是咱们想在Excle里面再打开WPF窗口,问题就不那么简单了。

咱们能够简单的实例化一个WPF窗体对象而后在Office应用程序的窗体上打开这个新的WPF窗体,此时Office应用的窗体就是WPF的宿主窗体。而后宿主窗体跟Office应用并非在一个UI线程上,子窗体极可能会在宿主窗体后面看不到。这个时候须要调用Win32函数,将Office应用的窗体设置为WPF子窗体的父窗体,这个函数的形式定义以下:

[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

因为Office应用程序是非托管程序,WPF窗体是托管程序,.NET提供了一个 WindowInteropHelper 包装类,它能够将一个托管程序窗体包装获得一个窗口句柄,以后,就能够调用上面的Win32函数 SetParent 设置窗口的父子关系了。

下面方法是一个完整的方法,能够经过反射实例化一个WPF窗体对象,而后设置此WPF窗体对象为Office应用程序的子窗体,并正常显示在Office应用程序上。

 

复制代码
   /// <summary>
        /// 在Excle窗口上显示WPF窗体
        /// </summary>
        /// <param name="assemplyName">窗体对象所在程序集</param>
        /// <param name="paramClassFullName">窗体对象全名称</param>
        public static void ExcelShowWPFWindow(string assemplyName, string paramClassFullName)
        {
            Application.Current.Dispatcher.Invoke(new Action(() => {
                try
                {
                    Assembly assembly = Assembly.Load(assemplyName);
                    Type classType = assembly.GetType(paramClassFullName);
                    object[] constuctParms = new object[] { };
                    dynamic view = Activator.CreateInstance(classType, constuctParms);
                    Window winBox = view as Window;
                    var winBoxIntreop = new WindowInteropHelper(winBox);
                    winBoxIntreop.EnsureHandle();
                    //将Excel句柄指定为当前窗体的父窗体的句柄,参考 https://blog.csdn.net/pengcwl/article/details/7817111
                    //ExcelApp 是一个Excle应用程序对象
                    var excelHwnd = new IntPtr(OfficeApp.ExcelApp.Hwnd);
                    winBoxIntreop.Owner = excelHwnd;
                    SetParent(winBoxIntreop.Handle, excelHwnd);
                    winBox.ShowDialog();
                }
                catch (Exception ex)
                {
                    MessageBox.Show("打开窗口错误:"+ex.Message);
                }
            }));
        }
    }
复制代码

 下面是打开的效果图:

不过,既然是的打开了一个模态窗口,咱们固然是想得到窗口的返回值。在WinForms比较简单,可是在WPF就须要作下设置。

首先看到上图的WPF窗体的XAML定义:

复制代码
<Window x:Class="MyWPF.View.Test"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyWPF.View"
         DataContext="{Binding TestViewModel, Source={StaticResource MyViewModelLocator}}"
        mc:Ignorable="d"
        Title="Test" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="80"/>
        </Grid.RowDefinitions>

        <TextBox Text="{Binding TestVale1}"/>
        <Button Content="sure" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="TestBtn" Click="testBtn_Click"/>
    </Grid>
</Window>
复制代码

窗体绑定了一个 TestViewModel1的ViewModel:

复制代码
public class TestViewModel : EntityBase,IWindowReturnValue<string>
{
        public TestViewModel()
        {
        }
       
        public string TestValue1
        {
            get { return getProperty<string>("TestValue1"); }
            set {
                setProperty("TestValue1",value,1000);
                ReturnValue = value;
            }
        }
     
        public string ReturnValue { get; set; }
        public string BackTest()
        {
           return TestValue1;
        }
    }
}
复制代码

TestViewModel 继承了SOD框架的实体类基类,它能够方便的实现MVVM的依赖属性,参考SOD的MVVM实现原理。本文重点看IWindowReturnValue<T>接口的定义:

  public interface IWindowReturnValue<T>
    {
        T ReturnValue { get; set; }
    }

接口很简单,就是定义一个返回值属性,这个属性在ViewModel 里面适当的时候给它赋值便可。

最后,咱们改写下前面的Excle打开窗体的函数就能够了,代码以下:

复制代码
 public static T ExcelShowWPFWindow<T>(string assemplyName, string paramClassFullName)
        {
            T result = default(T);
            Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                try
                {
                    Assembly assembly = Assembly.Load(assemplyName);
                    Type classType = assembly.GetType(paramClassFullName);
                    object[] constuctParms = new object[] { };
                    dynamic view = Activator.CreateInstance(classType, constuctParms);
                    Window winBox = view as Window;
                    var winBoxIntreop = new WindowInteropHelper(winBox);
                    winBoxIntreop.EnsureHandle();
//将Excel句柄指定为当前窗体的父窗体的句柄,参考 https://blog.csdn.net/pengcwl/article/details/7817111   var excelHwnd = new IntPtr(OfficeApp.ExcelApp.Hwnd);
                    winBoxIntreop.Owner = excelHwnd;
SetParent(winBoxIntreop.Handle, excelHwnd); var dataModel = winBox.DataContext as IWindowReturnValue<T>; winBox.ShowDialog(); result = dataModel.ReturnValue; } catch (Exception ex) { MessageBox.Show("打开窗口错误:" + ex.Message); } })); return result; } }
复制代码

最后运行此示例,测试经过。

 

 

完全关闭Excle进程的几个方法

以前研究过的问题,最近有朋友问,这里再总结下作一个笔记。

咱们在应用程序里面经过建立Excle应用对象打开Excle的状况下,若是不注意几个问题,可能没法完全关闭Excle进程,来考察下面的几种状况:

复制代码
        public static void startexcel()
        {
            var  excel = new Microsoft.Office.Interop.Excel.Application();
            excel.Visible = true;
            var book=  excel.Application.Workbooks.Open("D:\\Book1.xlsx");
        }
复制代码

上面的代码打开了一个工做簿,Excel启动了一个独立进程而且呈现界面给用户,不会再犯方法结束后关闭Excel。这种状况下本意是为了让用户决定什么时候关闭工做簿。

结果,当用户手工关闭工做簿后,Excle进程没有关闭,这是由于咱们的.NET 托管代码打开的Excle的非托管代码,.NET运行时没有释放相关的句柄,须要加上下面几行代码来释放:

复制代码
        public static void startexcel()
        {
            var  excel = new Microsoft.Office.Interop.Excel.Application();
            excel.Visible = true;
            var book=  excel.Application.Workbooks.Open("D:\\Book1.xlsx");

            System.Runtime.InteropServices.Marshal.ReleaseComObject(excel);
            excel = null;
            GC.Collect();
        }
复制代码

上面的代码,Marshal.ReleaseComObject 会释放COM组件对象,这里是excel,而后,代码设置 excel=null,这样紧接着执行垃圾回收才有效,不然,没法回收excel句柄。

注意,执行上面的代码并不会关闭了Excel进程,它只是释放了Excle进程句柄与.NET运行时的关系。

当用户在外面手工关闭Excle窗体后,Excle进程才会真正从任务管理器消失。

有朋友可能说,我没有加上面三行代码也可以关闭Excle进程啊。

没错,上面的代码只不过是当即释放了Excle这种非托管资源。注意到咱们的 excle对象是一个局部对象,因此当方法结束后,excle对象已经在方法堆栈上被清空了,只须要在外面合适的时候调用下垃圾回收,便可实现完全关闭Excle进程的效果:

startexcel();
GC.Collect();
Console.WriteLine("excel close ok.");

若是咱们的Excel进程不是由用户关闭而是要程序自动关闭怎么办?

这个时候只须要调用Excle应用程序对象的关闭方法便可。

完整的代码以下,而且下面的代码演示了Excle进程打开一个宏文件,而后再打开工做簿,处理事件,最后关闭Excle窗体,关闭进程清理资源的功能。

Excle的工做簿保存和关闭事件有时候比较有用,好比保存工做簿的时候就上传一份工做簿副本到服务器。

复制代码
 public static void startexcel()
        {
            var  excel = new Microsoft.Office.Interop.Excel.Application();
            excel.Visible = true;
            excel.Workbooks.Open("C:\\A1000.xla");
            var book=  excel.Application.Workbooks.Open("D:\\Book1.xlsx");
            excel.WorkbookBeforeSave += Excel_WorkbookBeforeSave;
            excel.WorkbookBeforeClose += Excel_WorkbookBeforeClose;
            book.Close();
            excel.Quit();
            System.Runtime.InteropServices.Marshal.ReleaseComObject(excel);
            excel = null;
            GC.Collect();
        }

        private static void Excel_WorkbookBeforeClose(Workbook Wb, ref bool Cancel)
        {
            Console.WriteLine("Excel 关闭,title:" + Wb.Title);
        }

        private static void Excel_WorkbookBeforeSave(Workbook Wb, bool SaveAsUI, ref bool Cancel)
        {
            Console.WriteLine("Excel保存,title:"+Wb.Title);
        }
复制代码

注:

本文的作法,也适用于关闭Word等其它Office程序。

 

 

(七)Net Core项目使用Controller之二

1、简介


 一、说明Post,Get定义的区别。

二、说明如何路由定义。

 

2、Get、Post定义


一、api不定义访问方式时,同时支持get 和 post。若是定义某种方式,则仅支持某种方式。具体看代码及运行效果。

这里有个知识点,何时使用get,何时使用post,我的习惯能get则get,不能get则post,至于put,delete,历来不用。

 

api代码

复制代码
    public class OneController : Controller
    {
        [HttpGet]
        public string GetString(string id)
        {
            return "get:" + id;
        }
        [HttpPost]
        public string PostString(string id)
        {
            return "post:" + id;
        }
        public string NullString(string id)
        {
            return "null:" + id;
        }
    }
复制代码

 

html测试代码

复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>示例代码</title>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
        $(function () {
            $.get("one/getstring", { id: "001" }, function (result) { console.log(result) });
            $.post("one/getstring", { id: "001" }, function (result) { console.log(result) });

            $.get("one/poststring", { id: "001" }, function (result) { console.log(result) });
            $.post("one/poststring", { id: "001" }, function (result) { console.log(result) });

            $.get("one/nullstring", { id: "001" }, function (result) { console.log(result) });
            $.post("one/nullstring", { id: "001" }, function (result) { console.log(result) });
        });
    </script>
</head><body></body>
</html>
复制代码

 

结果

定义了httpget,则post返回404,定义了httppost则get返回404,不定义则都能访问。

 

 3、整个Controller定义路由


 一、直接上代码,一看就懂。仔细观察访问路径。

 

整个Controller定义路由:api代码

复制代码
1     [Route("api/one")]
2     public class OneController : Controller
3     {
4         [Route("GetString")]
5         public string GetString(string id)
6         {
7             return "get:" + id;
8         }
9     }
复制代码

 

整个Controller定义路由:html代码

复制代码
 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="utf-8" />
 5     <title>示例代码</title>
 6     <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
 7     <script>
 8         $(function () {
 9             $.get("api/one/getstring", { id: "001" }, function (result) { console.log(result) });
10             $.post("api/one/getstring", { id: "001" }, function (result) { console.log(result) });
11         });
12     </script>
13 </head><body></body>
14 </html>
复制代码

 

整个Controller定义路由:效果

 

 

 

 

4、单个方法定义路由。


 一、直接上代码,一看就懂。仔细观察访问路径。

 

api代码

复制代码
1     public class OneController : Controller
2     {
3         [Route("api2/one/getstring")]
4         public string GetString(string id)
5         {
6             return "get:" + id;
7         }
8     }
复制代码

 

html代码,路径差异

复制代码
 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="utf-8" />
 5     <title>示例代码</title>
 6     <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
 7     <script>
 8         $(function () {
 9             $.get("api2/one/getstring", { id: "001" }, function (result) { console.log(result) });
10             $.post("api2/one/getstring", { id: "001" }, function (result) { console.log(result) });
11         });
12     </script>
13 </head><body></body>
14 </html>
复制代码

 

运行效果

 

 

 

 

 

5、既定义路由又定义访问方式


一、直接上代码,一看就懂。仔细观察访问路径。

 

api代码

 

复制代码
1     public class OneController : Controller
2     {
3         [HttpGet("api3/one/getstring")]
4         public string GetString(string id)
5         {
6             return "get:" + id;
7         }
8     }
复制代码

 

html 代码

复制代码
 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="utf-8" />
 5     <title>示例代码</title>
 6     <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
 7     <script>
 8         $(function () {
 9             $.get("api3/one/getstring", { id: "001" }, function (result) { console.log(result) });
10             $.post("api3/one/getstring", { id: "001" }, function (result) { console.log(result) });
11         });
12     </script>
13 </head><body></body>
14 </html>
复制代码

 

运行效果

 

 

 

 

6、结论


 一、经过以上对比,能够知道,路由的定义有几种方式,而且能够自由组合。

二、另外一种是在Startup中定义,见下图。

三、在生产过程当中,以上方法几乎不会用到,通常都是用Startup默认路由,由于咱们只要一个能够访问的惟一地址而已。

 

 

BitAdminCore框架做者。 框架演示:http://bit.bitdao.cn 框架使用:https://github.com/chenyinxin/cookiecutter-bitadmin-core 框架交流:QQ群202426919
相关文章
相关标签/搜索