基本版

根據官方文件的作法,當一個formflow完成後,那個Converstaion就會結束,不管之後再傳給bot什麼文字,Bot都不會有任何反應, 除非一個新的ConverstaionID重新建立

但是,在某些訊息環境,是沒有辦法更新ConverstaionID的. 這時候就需要自訂一個Dialog來處理FormComplete及其他的情形 就像官方文件所提到的Dialog是非常強大的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[Serializable]
public class SandwichDialog : IDialog
{
private readonly BuildForm<SandwichOrder> SandwichOrderForm;

internal SandwichDialog(BuildForm<SandwichOrder> SandwichOrderForm)
{
this.SandwichOrderForm = SandwichOrderForm;
}

public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}

public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<Message> argument)
{
var message = await argument;
var pizzaForm = new FormDialog<SandwichOrder>(new SandwichOrder(), this.SandwichOrderForm, FormOptions.PromptInStart);
context.Call<SandwichOrder>(pizzaForm, FormComplete);
}

private async Task FormComplete(IDialogContext context, IAwaitable<SandwichOrder> result)
{
SandwichOrder order = null;
try
{
order = await result;
}
catch (OperationCanceledException)
{
await context.PostAsync("You canceled the form!");
return;
}
catch (Exception ex)
{
await context.PostAsync(ex.Message);
return;
}

if (order != null)
{
await context.PostAsync(order.ToString());
}
else
{
await context.PostAsync("Form returned empty response!");
}

context.Wait(MessageReceivedAsync);
}
}

這個是當pizzaForm完成後,則執行FormComplete.

1
context.Call<T>(pizzaForm, FormComplete);

在 FormComplete 裡面,可以取得使用者所輸入的選項,所以後續要處理的動作會寫在此處

Gist

先從基本的開始,跟著下面的文章做,就可以完成基本的Bot功能了 http://docs.botframework.com/connector/getstarted/#navtitle

注意事項

  1. 當在新增【My Bot】時,Endpoint的網址一定要用https, 不然之後在測試Bot Connector時會出現403, 無法授權等奇怪的狀況.

  2. 如果使用web chat embed code時,要把他們所提供網址裡的s=[secret] 改成 t=[secret]

###程式基本的運作方式

Bot在與Bot Connector之間的溝通是透過傳遞Message. 這個Message裡面會包含很多資訊,也可以保留狀態(所以可以建立一連串的問題,就像在執行npm init時會問一堆問題一樣) 網站參考

總結: 一切都是在玩弄Message這個物件阿.

基於Docker安裝步驟變簡單了,所以是時候來玩Docker了. 在MVC Core的目錄下,新增一個檔案Dockfile, 內容如下

1
2
3
4
5
6
7
8
FROM microsoft/aspnet:1.0.0-rc1-update1

COPY . /app
WORKDIR /app
RUN ["dnu", "restore"]

EXPOSE 5000/tcp
ENTRYPOINT ["dnx", "-p", "project.json", "web","--server.urls", "http://0.0.0.0:5000"]

**server.urls 需要指定到0.0.0.0:port, 不然在docker run起來的時候,網頁會說Refused to Connect server.urls的設定方式可以參考這裡

開啟命令視窗,到有Dockerfile檔案的資料夾並執行下列指令

1
docker build -t <imageName> .

上列指令這會建立一個docker image file 接下來就要讓所建立出來的Image執行起來, 執行下列指令

1
docker run -t -d -p 5000:5000 <imageName>

詳細的Docker指令用法,請參閱官方網站

###今天下載了docker Toolbox for windows ,根據安裝指示安裝後,在執行時出現了一個錯誤訊息

1
hyper-v is installed. virtualbox won't boot a 64 bits vm in hyper-v is activated ....

排除方式為:修改 Program Files\Docker Toolbox\start.sh 在start.sh檔裡面,尋找

1
"${DOCKER_MACHINE}" create -d virtualbox "${VM}" 

更改成

1
"${DOCKER_MACHINE}" create --virtualbox-no-vtx-check -d virtualbox "${VM}"

即可排除此錯誤訊息

###當在command下docker command時,出現以下錯誤訊息

1
An error occurred trying to connect: Get http://127.0.0.1:2375/v1.22/containers/json: dial tcp 127.0.0.1:2375: connectex: No connection could be made because the target machine actively refused it.

排除方式為

  1. docker-machine start default or create new one
  2. docker-machine ls will show you your machine running
  3. docker-machine env --shell cmd default and you’ll see something like

SET DOCKER_TLS_VERIFY=1 SET DOCKER_HOST=tcp://xxx.xxx.xxx.xxx:2376 SET DOCKER_CERT_PATH=C:\Users\Arseny.docker\machine\machines\default SET DOCKER_MACHINE_NAME=default REM Run this command to configure your shell: REM FOR /f 「tokens=*」 %i IN (『docker-machine env --shell cmd default』) DO %i

4.Run

1
FOR /f "tokens=*" %i IN ('docker-machine env --shell cmd default') DO %i

5.Enjoy. 就可以正常的下docker指令了

目前開發所需的gulpfile.js版本 工作流程 for angular 1.x開發 [typescript]->[javascript]->[webpack]->bundle.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
'use strict';

var gulp = require('gulp'),
tsc = require('gulp-typescript'),
inject = require('gulp-inject'),
tsProject = tsc.createProject('tsconfig.json'),
webpack = require('webpack'),
gulpWebpack = require('webpack-stream'),
ngAnnotatePlugin = require('ng-annotate-webpack-plugin'),
path = require('path');

gulp.task('compile-ts', function () {
var sourceTsFiles = ['./app/src/**/*.ts', //path to typescript files
'./app/typings/**/*.ts']; //reference to library .d.ts files


var tsResult = gulp.src(sourceTsFiles)
.pipe(tsc(tsProject));

tsResult.dts.pipe(gulp.dest('./app/dist'));
return tsResult.js.pipe(gulp.dest('./app/dist'));
});

gulp.task('gen-ts-refs', function () {
var target = gulp.src('./app/src/app.d.ts');
var sources = gulp.src(['./app/src/**/*.ts'], { read: false });
return target.pipe(inject(sources, {
starttag: '//{',
endtag: '//}',
transform: function (filepath) {
if (filepath.indexOf('index') > -1) { return; }
if (filepath.indexOf('app.d.ts') > -1) { return; }
return '/// <reference path="../..' + filepath + '" />';
}
})).pipe(gulp.dest('./app/src/'));
});

gulp.task('watch', function () {
gulp.watch(['./app/src/**/*.ts'], ['webpack']);
});

gulp.task('webpack', ['compile-ts'], function () {
return gulp.src('./app/dist/app.js')
.pipe(gulpWebpack({
entry: {
bundled: './app/dist/app.js',
commands: './app/dist/libs.js'
},
output: {
filename: '[name].js',
},
resolve: {
// this tells Webpack where actually to find lodash because you'll need it in the ProvidePlugin
alias: {
lodash: path.resolve(__dirname, './node_modules/lodash'),
angular: path.resolve(__dirname, './node_modules/angular')
},
extensions: ['', '.js']
},
module: {
loaders: [
{ test: /[\/]angular\.js$/, loader: "exports?angular" }
]
},
plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
// this tells Webpack to provide the "_" variable globally in all your app files as lodash.
new webpack.ProvidePlugin({
_: "lodash",
}),
new ngAnnotatePlugin({
add: true
})
// new webpack.optimize.CommonsChunkPlugin('common.js'),
//new webpack.optimize.UglifyJsPlugin({
// compress: {
// warnings: false
// },
// output: { comments: false }
//})

]
}))
.pipe(gulp.dest('./Scripts'));
})

gulp.task('default', ['watch']);

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
"devDependencies": {
"gulp": "^3.9.0",
"gulp-inject": "^3.0.0",
"gulp-typescript": "^2.10.0",
"gulp-tsd": "^0.0.4",
"tsd": "^0.6.5",
"typescript": "^1.7.5",
"ng-annotate-webpack-plugin": "^0.1.2",
"path": "^0.11.14",
"webpack": "^1.11.0",
"webpack-stream": "^2.1.0"
},
"dependencies": {
"angular": "^1.4.8",
"lodash": "^4.0.0"
}

需要disable visual studio裡面對於typescript的compile,編輯csproj的第一個

1
2
加入這個讓vs不要在Build的時候編譯Typescript
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>

另外需要

1
2
3
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
在這個項目下,增加
<TypeScriptModuleKind>commonjs</TypeScriptModuleKind>

Angular 在 Components之間的值得傳遞方式分割成Inputs和Outputs. 寫法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Components({
....,
inputs:['init'],
outputs:['finish']
})
export class xxx(){
okEvent: EventEmitter<any> = new EventEmitter();

ok(){
// this should match the type define in EventEmitter
this.okEvent.emit('the value want to pass');
}
}

// in another components
<ddd (finish)="finish($event)" [init]="value pass in"></ddd>

$event => will catch the return value

另外一種寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Component, View, Input, Output, EventEmitter } from 'angular2/angular2';

@Components({
....
})
export class xxx(){
@Input() init;
// @Output(alias name)
@Output('finish') okEvent:EventEmitter<Any> = new EventEmitter();


ok(){
// this should match the type define in EventEmitter if use typescript
this.okEvent.emit('the value want to pass');
}
}

// in another components
<ddd (finish)="finish($event)" [init]="value pass in"></ddd>

$event => will catch the return value

After Scaffold from existing datbase, and then add migration at first time.

EF will create something like above. But in first migration will have everything that already existed in database. therefore, delete that file and add migration again. Now this time. you will get an empty migration file. WHy? because ContextModelSnapShot. It seems EF will compare all model files with snapshot file. and find the differences to create migration content file.

And Now it switch to Code first mode. ^^

EF 7 Doc

http://docs.asp.net/en/latest/security/authorization/simple.html 這裡描述怎麼設定頁面授權的方式,可是卻都沒有提到如果說沒授權的人要頁面轉至登入畫面的方式

經過網頁上的查詢及測試後. 在1.0.0-rc1-update1的版本裡,設定方式如下

  1. startup.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication();
.....
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCookieAuthentication(options =>
{
options.LoginPath = "/Home/Login";
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
});
....
}

到這裡為止就可以做出跟以前一樣遇到沒有授權的頁面就轉到登入畫面了

###參數說明

重點在於AutomaticAuthenticate 及 AutomaticChallenge 這兩個參數 他的說明如下:

  1. AutomaticAuthenticate: If true the authentication middlleware alter the request user coming in. If false the authentication middleware will only provide identity when explicitly indicated by the AuthenticationScheme.
  2. AutomaticChallenge: If true the authentication middleware should handle automatic challenge. If false the authentication middleware will only alter responses when explicitly indicated by the AuthenticationScheme.

這裡出現另外一個參數 AuthenticationScheme AuthenticationScheme: The AuthenticationScheme in the options corresponds to the logical name for a particular authentication scheme. A different value may be assigned in order to use the same authentication middleware type more than once in a pipepline.

這表示在Controller裡的[Authorize]可以指定AuthenticationScheme, 就可以做出很有彈性的權限設定轉址或是其他後續動作了

1
2
[Authorize(ActiveAuthenticationSchemes ="abc")]
public IActionResult Index(){}

##update 設定頁面授權的方式在這裡 http://docs.asp.net/en/latest/security/authentication/cookie.html

Reference

ASP.NET 5/MVC 6 自訂使用Claim驗証

之前沒有特別留意ngOptions在1.4版裡面修正了一些東西,包含track by的用法 先簡單的描述一下狀況

1
2
3
4
5
6
7
8
9
10
11
12
13
$scope.options = [
{id:1,display:"1"},
{id:2,display:"2"},
{id:3,display:"3"},
{id:4,display:"4"},
{id:5,display:"5"}
] ;

$scope.selected = 3;

// html
<select ng-options="m.id as m.display for m in options"
ng-model="selected"></select>

這種寫法應該算是很常見的用法

但是這樣子的寫法經過1.4版處理後, 仔細去看他的html會變成

1
2
3
4
5
6
7
<select ng-options="m.id as m.display for m in options" ng-model="selected">
<option label="1" value="number:1">1</option>
<option label="2" value="number:2">2</option>
<option label="3" value="number:3" selected="selected">3</option>
<option label="4" value="number:4">4</option>
<option label="5" value="number:5">5</option>
</select>

竟然多了型別…>"<, 這表示如果我的$scope.selected = '3’時,就會選不到東西了

好吧,那如果用track by呢

1
2
<select ng-options="m.id as m.display for m in options track by m.id" 
ng-model="selected"></select>

DOM

1
2
3
4
5
6
7
8
<select ng-options="m.id as m.display for m in options track by m.id" ng-model="selected">
<option value="?" selected="selected"></option>
<option label="1" value="1">1</option>
<option label="2" value="2">2</option>
<option label="3" value="3">3</option>
<option label="4" value="4">4</option>
<option label="5" value="5">5</option>
</select>

這樣子看起來正常多了,但是$scope.selected的值不管是使用 3 or 「3」 都選不到東西. 只有給他options裡面的某一個object他才會被選定。 所以看起來track by是用 for m的m當作選定的值,那 select as label不就沒用了,沒用就拿掉他

1
2
<select ng-options="m.display for m in options track by m.id" 
ng-model="selected"></select>

DOM

1
2
3
4
5
6
7
8
<select ng-options="m.id as m.display for m in options track by m.id" ng-model="selected">
<option value="?" selected="selected"></option>
<option label="1" value="1">1</option>
<option label="2" value="2">2</option>
<option label="3" value="3">3</option>
<option label="4" value="4">4</option>
<option label="5" value="5">5</option>
</select>

看起來都一樣了

結論 用track by: select裡的ng-model會是以object的型態呈現, 不需要再寫select as xxxx了. 不用track by: 就看所表示的select是怎樣的型態,ng-model就是怎樣的型態,但是多了型別的判斷

Classes

Class的組成元素:

  1. Constructor

  2. Prototype methods:

  3. Static methods: 不需要New class就可以使用, 類似C#的Static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}

get area() {
return this.calcArea()
}

calcArea() {
return this.height * this.width;
}

static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;

return Math.sqrt(dx*dx + dy*dy);
}

}

Hoisting: Class並沒有Hoisting特性,所以需要先定義才可以使用,這點須注意

Class inheritance

Class也可以有繼承的性質 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal { 
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + ' makes a noise.');
}
}

class Dog extends Animal {
speak() {
console.log(this.name + ' barks.');
}
}

Super的用法

Super用來呼叫Parent的function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Cat { 
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + ' makes a noise.');
}
}

class Lion extends Cat {
speak() {
super.speak(); // <= this call Cat's speak function
console.log(this.name + ' roars.');
}
}
0%