0
# Middleware
1
2
Middleware pipeline for implementing cross-cutting concerns like logging, telemetry, typing indicators, and automatic state saving. Middleware components can inspect and modify activities as they flow through the bot.
3
4
## Capabilities
5
6
### Middleware Base Class
7
8
Abstract base class that defines the interface for all middleware components in the Bot Framework pipeline, providing the foundation for implementing cross-cutting concerns.
9
10
```python { .api }
11
class Middleware:
12
async def on_turn(self, turn_context: TurnContext, next_middleware):
13
"""
14
Process the turn. Must be implemented by derived classes.
15
16
Args:
17
turn_context (TurnContext): Current turn context
18
next_middleware: Function to call the next middleware in pipeline
19
"""
20
```
21
22
### MiddlewareSet
23
24
Collection that manages middleware components and provides the pipeline execution mechanism for processing activities through registered middleware.
25
26
```python { .api }
27
class MiddlewareSet:
28
def __init__(self):
29
"""Initialize empty middleware set."""
30
31
def use(self, middleware):
32
"""
33
Add middleware to the set.
34
35
Args:
36
middleware (Middleware): Middleware to add
37
38
Returns:
39
MiddlewareSet: Self for method chaining
40
"""
41
42
async def receive_activity_with_status(self, turn_context: TurnContext, callback):
43
"""
44
Execute middleware pipeline with status handling.
45
46
Args:
47
turn_context (TurnContext): Current turn context
48
callback: Final callback to execute
49
50
Returns:
51
int: HTTP status code
52
"""
53
54
async def receive_activity(self, turn_context: TurnContext, callback):
55
"""
56
Execute middleware pipeline.
57
58
Args:
59
turn_context (TurnContext): Current turn context
60
callback: Final callback to execute
61
"""
62
```
63
64
### AutoSaveStateMiddleware
65
66
Automatically saves bot state after each turn completes, ensuring state changes are persisted without requiring manual save operations in bot logic.
67
68
```python { .api }
69
class AutoSaveStateMiddleware(Middleware):
70
def __init__(self, *bot_states):
71
"""
72
Initialize auto-save state middleware.
73
74
Args:
75
*bot_states: Variable number of BotState objects to auto-save
76
"""
77
78
async def on_turn(self, turn_context: TurnContext, next_middleware):
79
"""
80
Execute middleware and auto-save states.
81
82
Args:
83
turn_context (TurnContext): Current turn context
84
next_middleware: Next middleware function
85
"""
86
```
87
88
### ShowTypingMiddleware
89
90
Shows typing indicator to users while the bot is processing their message, providing better user experience by indicating bot activity.
91
92
```python { .api }
93
class ShowTypingMiddleware(Middleware):
94
def __init__(self, delay: float = 0.5, period: float = 2.0):
95
"""
96
Initialize show typing middleware.
97
98
Args:
99
delay (float): Delay before showing typing indicator (seconds)
100
period (float): How often to send typing indicator (seconds)
101
"""
102
103
async def on_turn(self, turn_context: TurnContext, next_middleware):
104
"""
105
Execute middleware with typing indicators.
106
107
Args:
108
turn_context (TurnContext): Current turn context
109
next_middleware: Next middleware function
110
"""
111
```
112
113
### TelemetryLoggerMiddleware
114
115
Logs telemetry data for bot activities, enabling monitoring, analytics, and debugging of bot interactions and performance.
116
117
```python { .api }
118
class TelemetryLoggerMiddleware(Middleware):
119
def __init__(self, telemetry_client, log_personal_information: bool = False):
120
"""
121
Initialize telemetry logger middleware.
122
123
Args:
124
telemetry_client (BotTelemetryClient): Telemetry client
125
log_personal_information (bool): Whether to log PII
126
"""
127
128
async def on_turn(self, turn_context: TurnContext, next_middleware):
129
"""
130
Execute middleware with telemetry logging.
131
132
Args:
133
turn_context (TurnContext): Current turn context
134
next_middleware: Next middleware function
135
"""
136
137
async def on_receive_activity(self, activity):
138
"""
139
Log received activity.
140
141
Args:
142
activity (Activity): Received activity
143
"""
144
145
async def on_send_activity(self, activity):
146
"""
147
Log sent activity.
148
149
Args:
150
activity (Activity): Sent activity
151
"""
152
153
async def on_update_activity(self, activity):
154
"""
155
Log updated activity.
156
157
Args:
158
activity (Activity): Updated activity
159
"""
160
161
async def on_delete_activity(self, activity):
162
"""
163
Log deleted activity.
164
165
Args:
166
activity (Activity): Deleted activity
167
"""
168
```
169
170
### RegisterClassMiddleware
171
172
Registers classes for dependency injection in the turn context, enabling access to services and utilities throughout the middleware pipeline and bot logic.
173
174
```python { .api }
175
class RegisterClassMiddleware(Middleware):
176
def __init__(self, classes_to_register: dict):
177
"""
178
Initialize register class middleware.
179
180
Args:
181
classes_to_register (dict): Dictionary of class name to class instance
182
"""
183
184
async def on_turn(self, turn_context: TurnContext, next_middleware):
185
"""
186
Execute middleware with class registration.
187
188
Args:
189
turn_context (TurnContext): Current turn context
190
next_middleware: Next middleware function
191
"""
192
```
193
194
### TranscriptLoggerMiddleware
195
196
Logs complete conversation transcripts for audit, compliance, and debugging purposes, storing the full conversation history.
197
198
```python { .api }
199
class TranscriptLoggerMiddleware(Middleware):
200
def __init__(self, transcript_logger):
201
"""
202
Initialize transcript logger middleware.
203
204
Args:
205
transcript_logger (TranscriptLogger): Transcript logger implementation
206
"""
207
208
async def on_turn(self, turn_context: TurnContext, next_middleware):
209
"""
210
Execute middleware with transcript logging.
211
212
Args:
213
turn_context (TurnContext): Current turn context
214
next_middleware: Next middleware function
215
"""
216
```
217
218
### AnonymousReceiveMiddleware
219
220
Wrapper that allows using anonymous functions or lambdas as middleware, providing a simple way to add custom middleware logic.
221
222
```python { .api }
223
class AnonymousReceiveMiddleware(Middleware):
224
def __init__(self, anonymous_method):
225
"""
226
Initialize anonymous middleware.
227
228
Args:
229
anonymous_method: Function to execute as middleware
230
"""
231
232
async def on_turn(self, turn_context: TurnContext, next_middleware):
233
"""
234
Execute anonymous middleware function.
235
236
Args:
237
turn_context (TurnContext): Current turn context
238
next_middleware: Next middleware function
239
"""
240
```
241
242
## Usage Examples
243
244
### Basic Middleware Setup
245
246
```python
247
from botbuilder.core import (
248
BotFrameworkAdapter, MiddlewareSet, AutoSaveStateMiddleware,
249
ShowTypingMiddleware, ConversationState, UserState, MemoryStorage
250
)
251
252
# Create adapter
253
adapter = BotFrameworkAdapter(settings)
254
255
# Create states
256
memory_storage = MemoryStorage()
257
conversation_state = ConversationState(memory_storage)
258
user_state = UserState(memory_storage)
259
260
# Add middleware to adapter
261
adapter.use(ShowTypingMiddleware(delay=0.5, period=2.0))
262
adapter.use(AutoSaveStateMiddleware(conversation_state, user_state))
263
```
264
265
### Custom Middleware Implementation
266
267
```python
268
from botbuilder.core import Middleware, TurnContext, MessageFactory
269
270
class LoggingMiddleware(Middleware):
271
async def on_turn(self, turn_context: TurnContext, next_middleware):
272
# Log incoming activity
273
print(f"Incoming: {turn_context.activity.type} - {turn_context.activity.text}")
274
275
# Continue pipeline
276
await next_middleware()
277
278
# Log after processing
279
print(f"Completed processing for turn")
280
281
class ProfanityFilterMiddleware(Middleware):
282
def __init__(self, bad_words: list):
283
self.bad_words = [word.lower() for word in bad_words]
284
285
async def on_turn(self, turn_context: TurnContext, next_middleware):
286
if turn_context.activity.type == "message":
287
text = turn_context.activity.text.lower()
288
289
# Check for profanity
290
if any(bad_word in text for bad_word in self.bad_words):
291
await turn_context.send_activity(
292
MessageFactory.text("Please keep the conversation respectful.")
293
)
294
return # Don't continue pipeline
295
296
# Continue pipeline
297
await next_middleware()
298
299
# Usage
300
adapter.use(LoggingMiddleware())
301
adapter.use(ProfanityFilterMiddleware(["badword1", "badword2"]))
302
```
303
304
### Telemetry Middleware Setup
305
306
```python
307
from botbuilder.core import TelemetryLoggerMiddleware, NullTelemetryClient
308
309
# Create telemetry client (use actual implementation in production)
310
telemetry_client = NullTelemetryClient()
311
312
# Create telemetry middleware
313
telemetry_middleware = TelemetryLoggerMiddleware(
314
telemetry_client,
315
log_personal_information=False # Set to False for privacy
316
)
317
318
# Add to adapter
319
adapter.use(telemetry_middleware)
320
```
321
322
### Anonymous Middleware
323
324
```python
325
from botbuilder.core import AnonymousReceiveMiddleware
326
327
# Simple logging middleware using lambda
328
logging_middleware = AnonymousReceiveMiddleware(
329
lambda turn_context, next_middleware: self.log_and_continue(turn_context, next_middleware)
330
)
331
332
async def log_and_continue(self, turn_context, next_middleware):
333
print(f"Processing message: {turn_context.activity.text}")
334
await next_middleware()
335
print("Message processed")
336
337
adapter.use(logging_middleware)
338
339
# Or inline with async lambda (Python 3.5+)
340
adapter.use(AnonymousReceiveMiddleware(
341
lambda turn_context, next_middleware: self.simple_log(turn_context, next_middleware)
342
))
343
```
344
345
### Middleware with Service Registration
346
347
```python
348
from botbuilder.core import RegisterClassMiddleware
349
350
class CustomService:
351
def __init__(self):
352
self.data = {}
353
354
def get_data(self, key):
355
return self.data.get(key)
356
357
def set_data(self, key, value):
358
self.data[key] = value
359
360
# Register service
361
custom_service = CustomService()
362
service_middleware = RegisterClassMiddleware({
363
"CustomService": custom_service
364
})
365
366
adapter.use(service_middleware)
367
368
# Access in bot
369
class ServiceBot(ActivityHandler):
370
async def on_message_activity(self, turn_context: TurnContext):
371
# Get service from turn context
372
custom_service = turn_context.services.get("CustomService")
373
374
if custom_service:
375
custom_service.set_data("last_message", turn_context.activity.text)
376
await turn_context.send_activity(
377
MessageFactory.text("Message saved to service!")
378
)
379
```
380
381
### Conditional Middleware
382
383
```python
384
class ConditionalMiddleware(Middleware):
385
def __init__(self, condition_func, target_middleware):
386
self.condition_func = condition_func
387
self.target_middleware = target_middleware
388
389
async def on_turn(self, turn_context: TurnContext, next_middleware):
390
# Check condition
391
if self.condition_func(turn_context):
392
# Execute target middleware
393
await self.target_middleware.on_turn(turn_context, next_middleware)
394
else:
395
# Skip target middleware
396
await next_middleware()
397
398
# Usage - only show typing for long messages
399
def is_long_message(turn_context):
400
return (turn_context.activity.type == "message" and
401
len(turn_context.activity.text or "") > 50)
402
403
conditional_typing = ConditionalMiddleware(
404
is_long_message,
405
ShowTypingMiddleware(delay=0.2, period=1.0)
406
)
407
408
adapter.use(conditional_typing)
409
```
410
411
### Error Handling Middleware
412
413
```python
414
class ErrorHandlingMiddleware(Middleware):
415
async def on_turn(self, turn_context: TurnContext, next_middleware):
416
try:
417
await next_middleware()
418
except Exception as e:
419
print(f"Error in bot: {e}")
420
421
# Send error message to user
422
await turn_context.send_activity(
423
MessageFactory.text("Sorry, something went wrong. Please try again.")
424
)
425
426
# Log error for debugging
427
# In production, you might want to send this to a logging service
428
429
# Add error handling as first middleware
430
adapter.use(ErrorHandlingMiddleware())
431
```
432
433
### Performance Monitoring Middleware
434
435
```python
436
import time
437
from botbuilder.core import Middleware
438
439
class PerformanceMiddleware(Middleware):
440
async def on_turn(self, turn_context: TurnContext, next_middleware):
441
start_time = time.time()
442
443
try:
444
await next_middleware()
445
finally:
446
duration = time.time() - start_time
447
print(f"Turn completed in {duration:.2f} seconds")
448
449
# Log slow turns
450
if duration > 2.0:
451
print(f"SLOW TURN: {duration:.2f}s for message: {turn_context.activity.text}")
452
453
adapter.use(PerformanceMiddleware())
454
```
455
456
### Middleware Ordering
457
458
```python
459
# Order matters! Middleware executes in registration order
460
# Error handling should be first
461
adapter.use(ErrorHandlingMiddleware())
462
463
# Performance monitoring
464
adapter.use(PerformanceMiddleware())
465
466
# Typing indicator
467
adapter.use(ShowTypingMiddleware())
468
469
# Service registration
470
adapter.use(RegisterClassMiddleware(services))
471
472
# Telemetry logging
473
adapter.use(TelemetryLoggerMiddleware(telemetry_client))
474
475
# Auto-save state (should be last)
476
adapter.use(AutoSaveStateMiddleware(conversation_state, user_state))
477
```
478
479
## Types
480
481
```python { .api }
482
class NextDelegate:
483
"""Delegate for calling next middleware in pipeline."""
484
async def __call__(self):
485
"""Execute next middleware."""
486
pass
487
488
class ReceiveActivityDelegate:
489
"""Delegate for receiving activities."""
490
async def __call__(self, turn_context: TurnContext):
491
"""Process turn context."""
492
pass
493
```