一張圖搞定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
相關文章
相關標籤/搜索