@polymorphism-tech/morph-spec 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +367 -367
  2. package/framework/agents.json +132 -127
  3. package/framework/rubrics/backend.json +41 -36
  4. package/framework/rules/ai-agents-standards.md +45 -15
  5. package/framework/rules/neon-standards.md +25 -26
  6. package/framework/standards/STANDARDS.json +1729 -1133
  7. package/framework/standards/ai-agents/agent-session.md +160 -0
  8. package/framework/standards/ai-agents/batching.md +193 -0
  9. package/framework/standards/ai-agents/context-providers.md +188 -0
  10. package/framework/standards/ai-agents/mcp-server.md +250 -0
  11. package/framework/standards/ai-agents/mcp-tools.md +197 -0
  12. package/framework/standards/ai-agents/middleware-patterns.md +207 -0
  13. package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
  14. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
  15. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
  16. package/framework/standards/ai-agents/modalities-vision.md +201 -0
  17. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
  18. package/framework/standards/ai-agents/production.md +114 -258
  19. package/framework/standards/ai-agents/prompts-in-db.md +214 -0
  20. package/framework/standards/ai-agents/providers/model-registry.md +158 -0
  21. package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
  22. package/framework/standards/ai-agents/service-tier-flex.md +135 -0
  23. package/framework/standards/ai-agents/setup.md +142 -316
  24. package/framework/standards/ai-agents/structured-output.md +249 -0
  25. package/framework/standards/ai-agents/sweet-spot.md +109 -0
  26. package/framework/standards/ai-agents/workflows.md +114 -284
  27. package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
  28. package/framework/standards/backend/api/validation.md +92 -88
  29. package/framework/standards/backend/database/postgresql/database.md +336 -332
  30. package/framework/standards/backend/database/vector-search-rag.md +599 -541
  31. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
  32. package/framework/standards/backend/dotnet/result-pattern.md +4 -0
  33. package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
  34. package/framework/standards/backend/dotnet/scrutor.md +4 -0
  35. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
  36. package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
  37. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
  38. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
  39. package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
  40. package/framework/standards/data/nosql/cache/redis.md +106 -97
  41. package/framework/standards/data/nosql/cosmos-db.md +127 -118
  42. package/framework/standards/data/vector-search/rag-chunking.md +113 -104
  43. package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
  44. package/framework/standards/frontend/design-system/animations.md +164 -160
  45. package/framework/standards/frontend/design-system/naming.md +68 -64
  46. package/framework/standards/frontend/gsap/gsap-core.md +14 -8
  47. package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
  48. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
  49. package/framework/standards/frontend/nextjs/app-router.md +127 -123
  50. package/framework/standards/frontend/nextjs/components.md +136 -132
  51. package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
  52. package/framework/standards/frontend/nextjs/forms.md +134 -128
  53. package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
  54. package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
  55. package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
  56. package/framework/standards/frontend/nextjs/project-structure.md +106 -102
  57. package/framework/standards/frontend/nextjs/state-management.md +76 -72
  58. package/framework/standards/frontend/nextjs/testing.md +115 -111
  59. package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
  60. package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
  61. package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
  62. package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
  63. package/framework/standards/integration/mcp/mcp-tools.md +471 -462
  64. package/framework/templates/REGISTRY.json +1745 -1239
  65. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
  66. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
  67. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
  68. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
  69. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
  70. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
  71. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
  72. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
  73. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
  74. package/package.json +89 -83
  75. package/src/core/templates/template-registry.js +289 -256
  76. package/src/lib/generators/context-generator.js +516 -518
  77. package/src/lib/generators/scaffold-generator.js +23 -26
  78. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
  79. package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
@@ -1,350 +1,354 @@
1
- # Hangfire Background Jobs Standard
2
-
3
- > **Scope:** universal
4
- > **Layer:** 2 (on keyword)
5
- > **Keywords:** hangfire, background job, recurring, fire-and-forget, scheduled
6
- > **Load When:** hangfire or background job keywords detected
7
-
8
- Background job processing for .NET applications with Hangfire.
9
-
10
- ---
11
-
12
- ## Overview
13
-
14
- Hangfire provides:
15
- - Fire-and-forget jobs
16
- - Delayed jobs
17
- - Recurring jobs (cron schedules)
18
- - Job continuations
19
- - Built-in dashboard
20
- - Retry logic with exponential backoff
21
-
22
- **Stack:** .NET 10
23
-
24
- ---
25
-
26
- ## Core Principles
27
-
28
- 1. **Stateless Jobs**: Job methods should be stateless and idempotent
29
- 2. **Minimal Dependencies**: Jobs should not depend on HTTP context or scoped services
30
- 3. **Explicit Retries**: Configure retry behavior per job type
31
- 4. **Monitor Dashboard**: Use Hangfire Dashboard for job monitoring
32
-
33
- ---
34
-
35
- ## Installation & Setup
36
-
37
- ### Install Packages
38
-
39
- ```bash
40
- dotnet add package Hangfire.Core
41
- dotnet add package Hangfire.SqlServer
42
- dotnet add package Hangfire.AspNetCore
43
- ```
44
-
45
- ### Program.cs Configuration
46
-
47
- ```csharp
48
- // Program.cs
49
- using Hangfire;
50
- using Hangfire.SqlServer;
51
-
52
- var builder = WebApplication.CreateBuilder(args);
53
-
54
- // Add Hangfire services
55
- builder.Services.AddHangfire(configuration => configuration
56
- .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
57
- .UseSimpleAssemblyNameTypeSerializer()
58
- .UseRecommendedSerializerSettings()
59
- .UseSqlServerStorage(
60
- builder.Configuration.GetConnectionString("HangfireConnection"),
61
- new SqlServerStorageOptions
62
- {
63
- CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
64
- SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
65
- QueuePollInterval = TimeSpan.Zero,
66
- UseRecommendedIsolationLevel = true,
67
- DisableGlobalLocks = true
68
- }));
69
-
70
- builder.Services.AddHangfireServer();
71
-
72
- var app = builder.Build();
73
-
74
- // Hangfire Dashboard (protect in production!)
75
- app.UseHangfireDashboard("/hangfire", new DashboardOptions
76
- {
77
- Authorization = new[] { new HangfireAuthorizationFilter() }
78
- });
79
-
80
- app.Run();
81
- ```
82
-
83
- ---
84
-
85
- ## Job Types
86
-
87
- ### Fire-and-Forget Jobs
88
-
89
- ```csharp
90
- // Enqueue immediately
91
- BackgroundJob.Enqueue<IEmailService>(x => x.SendWelcomeEmail(userId));
92
-
93
- // Alternative: Static method
94
- BackgroundJob.Enqueue(() => Console.WriteLine("Hello, world!"));
95
- ```
96
-
97
- ### Delayed Jobs
98
-
99
- ```csharp
100
- // Run after 1 hour
101
- BackgroundJob.Schedule<IReportService>(
102
- x => x.GenerateMonthlyReport(),
103
- TimeSpan.FromHours(1));
104
-
105
- // Run at specific time
106
- BackgroundJob.Schedule<INotificationService>(
107
- x => x.SendReminder(userId),
108
- DateTimeOffset.UtcNow.AddDays(7));
109
- ```
110
-
111
- ### Recurring Jobs
112
-
113
- ```csharp
114
- // Run daily at 2 AM UTC
115
- RecurringJob.AddOrUpdate<ICleanupService>(
116
- "cleanup-old-data",
117
- x => x.CleanupOldRecords(),
118
- Cron.Daily(2));
119
-
120
- // Run every 15 minutes
121
- RecurringJob.AddOrUpdate<ISyncService>(
122
- "sync-external-data",
123
- x => x.SyncData(),
124
- "*/15 * * * *");
125
- ```
126
-
127
- ### Job Continuations
128
-
129
- ```csharp
130
- var parentJobId = BackgroundJob.Enqueue<IOrderService>(
131
- x => x.ProcessOrder(orderId));
132
-
133
- BackgroundJob.ContinueJobWith<IEmailService>(
134
- parentJobId,
135
- x => x.SendOrderConfirmation(orderId));
136
- ```
137
-
138
- ---
139
-
140
- ## Job Service Pattern
141
-
142
- ```csharp
143
- // Services/Jobs/IEmailJobService.cs
144
- public interface IEmailJobService
145
- {
146
- Task SendWelcomeEmailAsync(string userId);
147
- Task SendPasswordResetAsync(string email);
148
- }
149
-
150
- // Services/Jobs/EmailJobService.cs
151
- public class EmailJobService : IEmailJobService
152
- {
153
- private readonly IDbContextFactory<AppDbContext> _dbFactory;
154
- private readonly IEmailSender _emailSender;
155
-
156
- public EmailJobService(
157
- IDbContextFactory<AppDbContext> dbFactory,
158
- IEmailSender emailSender)
159
- {
160
- _dbFactory = dbFactory;
161
- _emailSender = emailSender;
162
- }
163
-
164
- public async Task SendWelcomeEmailAsync(string userId)
165
- {
166
- // Use IDbContextFactory for background jobs
167
- await using var db = await _dbFactory.CreateDbContextAsync();
168
-
169
- var user = await db.Users.FindAsync(userId);
170
- if (user == null) return;
171
-
172
- await _emailSender.SendAsync(
173
- user.Email,
174
- "Welcome!",
175
- $"Hello {user.Name}!");
176
- }
177
- }
178
-
179
- // Register in DI
180
- builder.Services.AddScoped<IEmailJobService, EmailJobService>();
181
- ```
182
-
183
- ---
184
-
185
- ## Retry & Error Handling
186
-
187
- ### Custom Retry Attribute
188
-
189
- ```csharp
190
- [AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
191
- public async Task ProcessPaymentAsync(string paymentId)
192
- {
193
- // Job logic with automatic retry on failure
194
- }
195
- ```
196
-
197
- ### Manual Retry Logic
198
-
199
- ```csharp
200
- public async Task SendEmailWithRetryAsync(string email)
201
- {
202
- int maxRetries = 3;
203
- int attempt = 0;
204
-
205
- while (attempt < maxRetries)
206
- {
207
- try
208
- {
209
- await _emailSender.SendAsync(email, "Subject", "Body");
210
- return; // Success
211
- }
212
- catch (Exception ex)
213
- {
214
- attempt++;
215
- if (attempt >= maxRetries)
216
- {
217
- // Log and give up
218
- _logger.LogError(ex, "Failed to send email after {Attempts} attempts", maxRetries);
219
- throw;
220
- }
221
-
222
- await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
223
- }
224
- }
225
- }
226
- ```
227
-
228
- ---
229
-
230
- ## Dashboard Authorization
231
-
232
- ```csharp
233
- // Filters/HangfireAuthorizationFilter.cs
234
- using Hangfire.Dashboard;
235
-
236
- public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
237
- {
238
- public bool Authorize(DashboardContext context)
239
- {
240
- var httpContext = context.GetHttpContext();
241
-
242
- // Allow in development
243
- if (httpContext.Request.Host.Host.Contains("localhost"))
244
- return true;
245
-
246
- // Require authenticated user with Admin role
247
- return httpContext.User.Identity?.IsAuthenticated == true
248
- && httpContext.User.IsInRole("Admin");
249
- }
250
- }
251
- ```
252
-
253
- ---
254
-
255
- ## Best Practices
256
-
257
- ### Avoid Scoped Services
258
-
259
- ```csharp
260
- // ❌ BAD: Don't inject DbContext directly
261
- public class BadJobService
262
- {
263
- private readonly AppDbContext _db; // Scoped service!
264
-
265
- public BadJobService(AppDbContext db) => _db = db;
266
- }
267
-
268
- // ✅ GOOD: Use IDbContextFactory
269
- public class GoodJobService
270
- {
271
- private readonly IDbContextFactory<AppDbContext> _dbFactory;
272
-
273
- public GoodJobService(IDbContextFactory<AppDbContext> dbFactory)
274
- => _dbFactory = dbFactory;
275
-
276
- public async Task ProcessAsync()
277
- {
278
- await using var db = await _dbFactory.CreateDbContextAsync();
279
- // Use db...
280
- }
281
- }
282
- ```
283
-
284
- ### Idempotent Jobs
285
-
286
- ```csharp
287
- public async Task ProcessOrderAsync(string orderId)
288
- {
289
- await using var db = await _dbFactory.CreateDbContextAsync();
290
-
291
- var order = await db.Orders.FindAsync(orderId);
292
- if (order == null || order.Status == OrderStatus.Processed)
293
- {
294
- // Already processed or doesn't exist - skip
295
- return;
296
- }
297
-
298
- // Process order...
299
- order.Status = OrderStatus.Processed;
300
- await db.SaveChangesAsync();
301
- }
302
- ```
303
-
304
- ---
305
-
306
- ## Cron Schedule Examples
307
-
308
- ```csharp
309
- Cron.Minutely() // Every minute
310
- Cron.Hourly() // Every hour at minute 0
311
- Cron.Daily() // Every day at 00:00 UTC
312
- Cron.Daily(14) // Every day at 14:00 UTC
313
- Cron.Weekly() // Every Sunday at 00:00 UTC
314
- Cron.Monthly() // First day of month at 00:00 UTC
315
- Cron.Yearly() // January 1st at 00:00 UTC
316
-
317
- // Custom cron
318
- "*/5 * * * *" // Every 5 minutes
319
- "0 */2 * * *" // Every 2 hours
320
- "0 9-17 * * 1-5" // 9 AM to 5 PM, Mon-Fri
321
- ```
322
-
323
- ---
324
-
325
- ## Monitoring & Troubleshooting
326
-
327
- ### Dashboard Access
328
-
329
- Access the dashboard at `/hangfire` (configured in Program.cs).
330
-
331
- ### Common Issues
332
-
333
- | Issue | Cause | Solution |
334
- |-------|-------|----------|
335
- | Jobs not processing | Hangfire server not started | Ensure `AddHangfireServer()` is called |
336
- | DbContext errors | Using scoped DbContext | Use `IDbContextFactory` |
337
- | Jobs timing out | Long-running job | Split into smaller jobs with continuations |
338
- | Memory leaks | Not disposing DbContext | Use `await using` for DbContext |
339
-
340
- ---
341
-
342
- ## References
343
-
344
- - [Hangfire Documentation](https://docs.hangfire.io/)
345
- - [Hangfire Best Practices](https://docs.hangfire.io/en/latest/best-practices.html)
346
- - [Cron Expression Generator](https://crontab.guru/)
347
-
348
- ---
349
-
350
- *MORPH-SPEC by Polymorphism Tech*
1
+ # Hangfire Background Jobs Standard
2
+
3
+ > **Scope:** universal
4
+ > **Layer:** 2 (on keyword)
5
+ > **Keywords:** hangfire, background job, recurring, fire-and-forget, scheduled
6
+ > **Load When:** hangfire or background job keywords detected
7
+
8
+ **Verified against:** .NET 10 + Hangfire 1.8 (Core/SqlServer/AspNetCore). Last-verified: 2026-05-20.
9
+
10
+ ---
11
+
12
+ Background job processing for .NET applications with Hangfire.
13
+
14
+ ---
15
+
16
+ ## Overview
17
+
18
+ Hangfire provides:
19
+ - Fire-and-forget jobs
20
+ - Delayed jobs
21
+ - Recurring jobs (cron schedules)
22
+ - Job continuations
23
+ - Built-in dashboard
24
+ - Retry logic with exponential backoff
25
+
26
+ **Stack:** .NET 10
27
+
28
+ ---
29
+
30
+ ## Core Principles
31
+
32
+ 1. **Stateless Jobs**: Job methods should be stateless and idempotent
33
+ 2. **Minimal Dependencies**: Jobs should not depend on HTTP context or scoped services
34
+ 3. **Explicit Retries**: Configure retry behavior per job type
35
+ 4. **Monitor Dashboard**: Use Hangfire Dashboard for job monitoring
36
+
37
+ ---
38
+
39
+ ## Installation & Setup
40
+
41
+ ### Install Packages
42
+
43
+ ```bash
44
+ dotnet add package Hangfire.Core
45
+ dotnet add package Hangfire.SqlServer
46
+ dotnet add package Hangfire.AspNetCore
47
+ ```
48
+
49
+ ### Program.cs Configuration
50
+
51
+ ```csharp
52
+ // Program.cs
53
+ using Hangfire;
54
+ using Hangfire.SqlServer;
55
+
56
+ var builder = WebApplication.CreateBuilder(args);
57
+
58
+ // Add Hangfire services
59
+ builder.Services.AddHangfire(configuration => configuration
60
+ .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
61
+ .UseSimpleAssemblyNameTypeSerializer()
62
+ .UseRecommendedSerializerSettings()
63
+ .UseSqlServerStorage(
64
+ builder.Configuration.GetConnectionString("HangfireConnection"),
65
+ new SqlServerStorageOptions
66
+ {
67
+ CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
68
+ SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
69
+ QueuePollInterval = TimeSpan.Zero,
70
+ UseRecommendedIsolationLevel = true,
71
+ DisableGlobalLocks = true
72
+ }));
73
+
74
+ builder.Services.AddHangfireServer();
75
+
76
+ var app = builder.Build();
77
+
78
+ // Hangfire Dashboard (protect in production!)
79
+ app.UseHangfireDashboard("/hangfire", new DashboardOptions
80
+ {
81
+ Authorization = new[] { new HangfireAuthorizationFilter() }
82
+ });
83
+
84
+ app.Run();
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Job Types
90
+
91
+ ### Fire-and-Forget Jobs
92
+
93
+ ```csharp
94
+ // Enqueue immediately
95
+ BackgroundJob.Enqueue<IEmailService>(x => x.SendWelcomeEmail(userId));
96
+
97
+ // Alternative: Static method
98
+ BackgroundJob.Enqueue(() => Console.WriteLine("Hello, world!"));
99
+ ```
100
+
101
+ ### Delayed Jobs
102
+
103
+ ```csharp
104
+ // Run after 1 hour
105
+ BackgroundJob.Schedule<IReportService>(
106
+ x => x.GenerateMonthlyReport(),
107
+ TimeSpan.FromHours(1));
108
+
109
+ // Run at specific time
110
+ BackgroundJob.Schedule<INotificationService>(
111
+ x => x.SendReminder(userId),
112
+ DateTimeOffset.UtcNow.AddDays(7));
113
+ ```
114
+
115
+ ### Recurring Jobs
116
+
117
+ ```csharp
118
+ // Run daily at 2 AM UTC
119
+ RecurringJob.AddOrUpdate<ICleanupService>(
120
+ "cleanup-old-data",
121
+ x => x.CleanupOldRecords(),
122
+ Cron.Daily(2));
123
+
124
+ // Run every 15 minutes
125
+ RecurringJob.AddOrUpdate<ISyncService>(
126
+ "sync-external-data",
127
+ x => x.SyncData(),
128
+ "*/15 * * * *");
129
+ ```
130
+
131
+ ### Job Continuations
132
+
133
+ ```csharp
134
+ var parentJobId = BackgroundJob.Enqueue<IOrderService>(
135
+ x => x.ProcessOrder(orderId));
136
+
137
+ BackgroundJob.ContinueJobWith<IEmailService>(
138
+ parentJobId,
139
+ x => x.SendOrderConfirmation(orderId));
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Job Service Pattern
145
+
146
+ ```csharp
147
+ // Services/Jobs/IEmailJobService.cs
148
+ public interface IEmailJobService
149
+ {
150
+ Task SendWelcomeEmailAsync(string userId);
151
+ Task SendPasswordResetAsync(string email);
152
+ }
153
+
154
+ // Services/Jobs/EmailJobService.cs
155
+ public class EmailJobService : IEmailJobService
156
+ {
157
+ private readonly IDbContextFactory<AppDbContext> _dbFactory;
158
+ private readonly IEmailSender _emailSender;
159
+
160
+ public EmailJobService(
161
+ IDbContextFactory<AppDbContext> dbFactory,
162
+ IEmailSender emailSender)
163
+ {
164
+ _dbFactory = dbFactory;
165
+ _emailSender = emailSender;
166
+ }
167
+
168
+ public async Task SendWelcomeEmailAsync(string userId)
169
+ {
170
+ // Use IDbContextFactory for background jobs
171
+ await using var db = await _dbFactory.CreateDbContextAsync();
172
+
173
+ var user = await db.Users.FindAsync(userId);
174
+ if (user == null) return;
175
+
176
+ await _emailSender.SendAsync(
177
+ user.Email,
178
+ "Welcome!",
179
+ $"Hello {user.Name}!");
180
+ }
181
+ }
182
+
183
+ // Register in DI
184
+ builder.Services.AddScoped<IEmailJobService, EmailJobService>();
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Retry & Error Handling
190
+
191
+ ### Custom Retry Attribute
192
+
193
+ ```csharp
194
+ [AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
195
+ public async Task ProcessPaymentAsync(string paymentId)
196
+ {
197
+ // Job logic with automatic retry on failure
198
+ }
199
+ ```
200
+
201
+ ### Manual Retry Logic
202
+
203
+ ```csharp
204
+ public async Task SendEmailWithRetryAsync(string email)
205
+ {
206
+ int maxRetries = 3;
207
+ int attempt = 0;
208
+
209
+ while (attempt < maxRetries)
210
+ {
211
+ try
212
+ {
213
+ await _emailSender.SendAsync(email, "Subject", "Body");
214
+ return; // Success
215
+ }
216
+ catch (Exception ex)
217
+ {
218
+ attempt++;
219
+ if (attempt >= maxRetries)
220
+ {
221
+ // Log and give up
222
+ _logger.LogError(ex, "Failed to send email after {Attempts} attempts", maxRetries);
223
+ throw;
224
+ }
225
+
226
+ await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
227
+ }
228
+ }
229
+ }
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Dashboard Authorization
235
+
236
+ ```csharp
237
+ // Filters/HangfireAuthorizationFilter.cs
238
+ using Hangfire.Dashboard;
239
+
240
+ public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
241
+ {
242
+ public bool Authorize(DashboardContext context)
243
+ {
244
+ var httpContext = context.GetHttpContext();
245
+
246
+ // Allow in development
247
+ if (httpContext.Request.Host.Host.Contains("localhost"))
248
+ return true;
249
+
250
+ // Require authenticated user with Admin role
251
+ return httpContext.User.Identity?.IsAuthenticated == true
252
+ && httpContext.User.IsInRole("Admin");
253
+ }
254
+ }
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Best Practices
260
+
261
+ ### Avoid Scoped Services
262
+
263
+ ```csharp
264
+ // ❌ BAD: Don't inject DbContext directly
265
+ public class BadJobService
266
+ {
267
+ private readonly AppDbContext _db; // Scoped service!
268
+
269
+ public BadJobService(AppDbContext db) => _db = db;
270
+ }
271
+
272
+ // ✅ GOOD: Use IDbContextFactory
273
+ public class GoodJobService
274
+ {
275
+ private readonly IDbContextFactory<AppDbContext> _dbFactory;
276
+
277
+ public GoodJobService(IDbContextFactory<AppDbContext> dbFactory)
278
+ => _dbFactory = dbFactory;
279
+
280
+ public async Task ProcessAsync()
281
+ {
282
+ await using var db = await _dbFactory.CreateDbContextAsync();
283
+ // Use db...
284
+ }
285
+ }
286
+ ```
287
+
288
+ ### Idempotent Jobs
289
+
290
+ ```csharp
291
+ public async Task ProcessOrderAsync(string orderId)
292
+ {
293
+ await using var db = await _dbFactory.CreateDbContextAsync();
294
+
295
+ var order = await db.Orders.FindAsync(orderId);
296
+ if (order == null || order.Status == OrderStatus.Processed)
297
+ {
298
+ // Already processed or doesn't exist - skip
299
+ return;
300
+ }
301
+
302
+ // Process order...
303
+ order.Status = OrderStatus.Processed;
304
+ await db.SaveChangesAsync();
305
+ }
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Cron Schedule Examples
311
+
312
+ ```csharp
313
+ Cron.Minutely() // Every minute
314
+ Cron.Hourly() // Every hour at minute 0
315
+ Cron.Daily() // Every day at 00:00 UTC
316
+ Cron.Daily(14) // Every day at 14:00 UTC
317
+ Cron.Weekly() // Every Sunday at 00:00 UTC
318
+ Cron.Monthly() // First day of month at 00:00 UTC
319
+ Cron.Yearly() // January 1st at 00:00 UTC
320
+
321
+ // Custom cron
322
+ "*/5 * * * *" // Every 5 minutes
323
+ "0 */2 * * *" // Every 2 hours
324
+ "0 9-17 * * 1-5" // 9 AM to 5 PM, Mon-Fri
325
+ ```
326
+
327
+ ---
328
+
329
+ ## Monitoring & Troubleshooting
330
+
331
+ ### Dashboard Access
332
+
333
+ Access the dashboard at `/hangfire` (configured in Program.cs).
334
+
335
+ ### Common Issues
336
+
337
+ | Issue | Cause | Solution |
338
+ |-------|-------|----------|
339
+ | Jobs not processing | Hangfire server not started | Ensure `AddHangfireServer()` is called |
340
+ | DbContext errors | Using scoped DbContext | Use `IDbContextFactory` |
341
+ | Jobs timing out | Long-running job | Split into smaller jobs with continuations |
342
+ | Memory leaks | Not disposing DbContext | Use `await using` for DbContext |
343
+
344
+ ---
345
+
346
+ ## References
347
+
348
+ - [Hangfire Documentation](https://docs.hangfire.io/)
349
+ - [Hangfire Best Practices](https://docs.hangfire.io/en/latest/best-practices.html)
350
+ - [Cron Expression Generator](https://crontab.guru/)
351
+
352
+ ---
353
+
354
+ *MORPH-SPEC by Polymorphism Tech*