| name | dotnet-wpf-dify-api-integration |
| description | こんなときに使う: WPFアプリにDify APIを追加。DPAPI設定とSSEストリーミング対応。Dify連携構築時に使用。 |
WPFアプリケーションへのDify API連携追加
WPFアプリケーションにDify API連携を追加するためのエンドツーエンドワークフロー:DPAPIによるセキュア設定、MVVM設定UI、ファイルアップロード、SSEベースのワークフローストリーミング。
こんなときに使う
以下の場合にこのスキルを使用してください:
- 既存のWPFアプリケーションにDify API連携を追加するとき
- SSEストリーミング経由でDifyワークフローを呼び出す新規WPFプロジェクトを作成するとき
- Dify設定用のDPAPI暗号化APIキー保存を生成するとき
- Dify API接続管理用のMVVM設定ダイアログを構築するとき
- リアルタイム進捗表示付きのファイルアップロードとストリーミングワークフロー実行を実装するとき
Related Skills
dotnet-wpf-secure-config — 必須:DPAPI暗号化基盤(先に適用)
dotnet-oracle-wpf-integration — 同じアプリでSecureConfigServiceを共有
tdd-standard-practice — Red-Green-Refactorで生成コードをテスト
git-commit-practices — 各ステップをアトミックな変更としてコミット
skill — このスキルの品質を検証・改善
Core Principles
- 階層化アーキテクチャ — Presentation、Infrastructure、Domainの関心事を分離(基礎と型)
- デフォルトでセキュア — APIキーはDPAPI暗号化。平文保存は禁止(ニュートラル)
- 段階的な統合 — 設定 → クライアント → UI、一層ずつ確実に(継続は力)
- MVVM規律 — ViewModelがすべてのUIロジックを駆動。code-behindは最小限(基礎と型)
- 再利用可能なコンポーネント — 各クラスがWPFプロジェクト間で独立して動作(成長の複利)
Workflow: Integrate Dify API into WPF
Step 1 — 前提条件確認とDify固有ファイル追加
dotnet-wpf-secure-config 適用済みプロジェクトにDify固有ファイルを追加するときに使用します。
前提条件(先に完了必須):
dotnet-wpf-secure-config スキル適用済み
Infrastructure/Configuration/ フォルダに以下が存在:
DpapiEncryptor.cs
SecureConfigService.cs
ISecureConfigService.cs
AppConfigModel.cs
追加するファイル(Dify固有):
YourApp/
├── Infrastructure/
│ ├── Configuration/
│ │ └── DifyConfigModel.cs # 🆕 追加
│ └── Difys/ # 🆕 フォルダ作成
│ └── DifyApiService.cs # 🆕 追加
└── Presentation/
├── ViewModels/
│ └── DifyConfigViewModel.cs # 🆕 追加
└── Views/
├── DifyConfigDialog.xaml # 🆕 追加
└── DifyConfigDialog.xaml.cs # 🆕 追加
NuGetパッケージ(未インストールの場合):
Install-Package CommunityToolkit.Mvvm
Install-Package Microsoft.Extensions.DependencyInjection
Values: 基礎と型 / 成長の複利
Step 2 — Dify設定モデルの追加
DPAPI暗号化APIキー付きのDify API設定を定義するときに使用します。
前提条件:先にdotnet-wpf-secure-configを適用してDpapiEncryptor、SecureConfigService、AppConfigModelをセットアップしてください。
DifyConfigModel.cs — Dify固有の設定データ(Infrastructure/Configuration/に追加):
public class DifyConfigModel
{
public string BaseUrl { get; set; } = string.Empty;
public string ApiKeyEncrypted { get; set; } = string.Empty;
public string EmployeeId { get; set; } = string.Empty;
public string GetDecryptedApiKey()
=> DpapiEncryptor.Decrypt(ApiKeyEncrypted);
public void SetApiKey(string plainApiKey)
=> ApiKeyEncrypted = DpapiEncryptor.Encrypt(plainApiKey);
public bool IsValid()
=> !string.IsNullOrWhiteSpace(BaseUrl)
&& !string.IsNullOrWhiteSpace(ApiKeyEncrypted);
}
AppConfigModelを更新(Difyプロパティを追加):
public class AppConfigModel
{
public DifyConfigModel DifyApi { get; set; } = new();
public string Version { get; set; } = "1.0";
}
ISecureConfigServiceとSecureConfigServiceを更新(Difyメソッドを追加):
Task<DifyConfigModel> LoadDifyConfigAsync();
Task SaveDifyConfigAsync(DifyConfigModel config);
public async Task<DifyConfigModel> LoadDifyConfigAsync()
{
var appConfig = await LoadAppConfigAsync();
return appConfig.DifyApi;
}
public async Task SaveDifyConfigAsync(DifyConfigModel config)
{
var appConfig = await LoadAppConfigAsync();
appConfig.DifyApi = config;
await SaveAppConfigAsync(appConfig);
}
DpapiEncryptor、SecureConfigServiceフレームワーク、AppConfigModelベースについてはdotnet-wpf-secure-configを参照してください。
Values: 基礎と型 / ニュートラル
Step 3 — APIクライアントの実装(アップロード + SSE)
Dify APIへのファイルアップロードとワークフロー実行を接続するときに使用します。
ファイルアップロードとストリーミングワークフロー実行を持つDifyApiServiceを作成します。
注意: サンプルでは簡潔さのためusing var client = new HttpClient()を使用しています。本番環境ではソケット枯渇を防ぐため、DIに登録したIHttpClientFactoryの使用を推奨します。
ファイルアップロード (/v1/files/upload):
public class DifyApiService
{
private readonly ISecureConfigService _configService;
public DifyApiService(ISecureConfigService configService)
=> _configService = configService;
public async Task<string> UploadFileAsync(string filePath)
{
var config = await _configService.LoadDifyConfigAsync();
string apiKey = config.GetDecryptedApiKey();
string baseUrl = config.BaseUrl.TrimEnd('/');
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
using var form = new MultipartFormDataContent();
form.Add(new StringContent(config.EmployeeId), "user");
var fileContent = new ByteArrayContent(await File.ReadAllBytesAsync(filePath));
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
form.Add(fileContent, "file", Path.GetFileName(filePath));
var res = await client.PostAsync($"{baseUrl}/v1/files/upload", form);
res.EnsureSuccessStatusCode();
var json = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
return json.RootElement.GetProperty("id").GetString()!;
}
}
SSE付きワークフロー実行 (/v1/workflows/run):
public async Task<string> RunWorkflowStreamingAsync(
string uploadFileId, Dictionary<string, object> inputs,
IProgress<string>? progress = null)
{
var config = await _configService.LoadDifyConfigAsync();
string apiKey = config.GetDecryptedApiKey();
string baseUrl = config.BaseUrl.TrimEnd('/');
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(300) };
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
inputs["pdf_file"] = new {
transfer_method = "local_file", upload_file_id = uploadFileId, type = "document"
};
var body = new { inputs, response_mode = "streaming",
user = config.EmployeeId };
var content = new StringContent(
JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
using var req = new HttpRequestMessage(HttpMethod.Post,
$"{baseUrl}/v1/workflows/run") { Content = content };
var res = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
res.EnsureSuccessStatusCode();
return await ReadSseStreamAsync(res, progress);
}
SSEストリームリーダー — data:行を解析し、workflow_started / node_started / node_finished / workflow_finishedイベントをIProgress<string>にルーティングします。完全な実装はreferences/detailed-patterns.mdを参照してください。
Values: 継続は力 / 温故知新
Step 4 — MVVM設定UIの構築
Dify API設定ダイアログを作成・更新するときに使用します。
Dify API設定用のViewModelとXAMLダイアログを作成します。
DifyConfigViewModel.cs:
public partial class DifyConfigViewModel : ObservableObject
{
private readonly ISecureConfigService _configService;
[ObservableProperty] private string baseUrl = string.Empty;
[ObservableProperty] private string apiKey = string.Empty;
[ObservableProperty] private string employeeId = string.Empty;
[ObservableProperty] private string statusMessage = string.Empty;
[ObservableProperty] private bool isSaving;
public DifyConfigViewModel(ISecureConfigService configService)
=> _configService = configService;
public async Task LoadConfigAsync()
{
var cfg = await _configService.LoadDifyConfigAsync();
BaseUrl = cfg.BaseUrl;
EmployeeId = cfg.EmployeeId;
try
{
ApiKey = cfg.GetDecryptedApiKey();
}
catch (CryptographicException)
{
ApiKey = string.Empty;
StatusMessage = "保存されたAPIキーの復号化に失敗しました。再入力してください。";
}
}
[RelayCommand]
private async Task SaveAsync()
{
if (string.IsNullOrWhiteSpace(BaseUrl) || string.IsNullOrWhiteSpace(ApiKey))
{ StatusMessage = "ベースURLとAPIキーは必須です。"; return; }
IsSaving = true;
try
{
var config = new DifyConfigModel
{ BaseUrl = BaseUrl, EmployeeId = EmployeeId };
config.SetApiKey(ApiKey);
await _configService.SaveDifyConfigAsync(config);
StatusMessage = "保存しました。";
}
catch (Exception ex)
{
StatusMessage = $"保存に失敗しました: {ex.Message}";
}
finally
{
IsSaving = false;
}
}
}
DifyConfigDialog.xaml.cs — 最小限のcode-behind(PasswordBoxブリッジのみ):
public partial class DifyConfigDialog : Window
{
public DifyConfigDialog(DifyConfigViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
Loaded += async (_, _) => await viewModel.LoadConfigAsync();
viewModel.PropertyChanged += (_, e) =>
{ if (e.PropertyName == nameof(viewModel.ApiKey)) ApiKeyBox.Password = viewModel.ApiKey; };
ApiKeyBox.PasswordChanged += (_, _) => viewModel.ApiKey = ApiKeyBox.Password;
}
private void Close_Click(object sender, RoutedEventArgs e) => Close();
}
Values: 基礎と型 / 成長の複利
Step 5 — DI配線と起動
サービスを登録し、設定ダイアログを初めて起動するときに使用します。
App.xaml.csでサービスを登録し、設定ダイアログを接続します。
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var services = new ServiceCollection();
services.AddSingleton<ISecureConfigService, SecureConfigService>();
services.AddSingleton<DifyApiService>();
services.AddTransient<DifyConfigViewModel>();
_serviceProvider = services.BuildServiceProvider();
}
var vm = _serviceProvider.GetRequiredService<DifyConfigViewModel>();
new DifyConfigDialog(vm).ShowDialog();
Values: 成長の複利 / 継続は力
Step 6 — アプリケーション固有のカスタマイズ
生成されたコードを本番デプロイ用に準備するときに使用します。
出荷前にこれらのプレースホルダーを置き換えてください:
| 項目 | ファイル | 変更内容 |
|---|
| アプリ名 | SecureConfigService.cs | 設定パス内の"YourAppName" |
| ソルト値 | DpapiEncryptor.cs | Entropyバイト配列の値 |
| 名前空間 | 全.csファイル | YourApp → 実際の名前空間 |
| ワークフロー入力 | DifyApiService.cs | inputs辞書のキー |
| 社員番号 | DifyConfigDialog.xaml | 社員番号入力用TextBox追加 |
Values: ニュートラル / 基礎と型
Good Practices
1. 保存前にBaseUrlスキームを検証
What: ViewModelのSaveAsyncメソッドでHTTPS以外のURLを拒否します。
Why: APIキーはネットワーク上を流れるため、HTTPでは傍受のリスクがあります。
Values: ニュートラル(セキュリティを標準化)
2. 操作ごとに明示的なタイムアウトを設定
What: ワークフロー300秒、アップロード30秒、接続テスト10秒。
Why: 無限ハングを防止し、ユーザー体験を改善します。
Values: 継続は力(安定した動作を継続)
3. すべての長時間操作でIProgressを使用
What: 開始と終了だけでなく、各SSEイベントで進捗をレポートします。
Why: ユーザーは固まった画面ではなく、ノードレベルの進捗を確認できます。
Values: 成長の複利(UXの知見がチームに蓄積)
Common Pitfalls
1. appsettings.jsonにAPIキーを保存
Problem: ソース管理される設定ファイルに平文のAPIキー。
Solution: Step 2のDpapiEncryptor + SecureConfigServiceを使用します。
{ "DifyApi": { "ApiKey": "app-xxxxxxxxxxxx" } }
{ "DifyApi": { "ApiKeyEncrypted": "AQAAANCMnd8B..." } }
2. SSEストリーミング中にUIスレッドをブロック
Problem: 非同期SSE呼び出しに.Resultや.Wait()を使用するとUIがフリーズ。
Solution: awaitとIProgress<string>でノンブロッキング更新を行います。
var result = difyService.RunWorkflowStreamingAsync(...).Result;
var result = await difyService.RunWorkflowStreamingAsync(..., progress);
3. CryptographicExceptionを無視
Problem: ユーザーAが暗号化したDPAPIデータはユーザーBでは復号化できない。
Solution: 例外をキャッチし、ユーザーに認証情報の再入力を促します。
4. BaseUrlを設定なしでハードコード
Problem: https://api.dify.aiがソースコードに埋め込まれ、環境ごとの変更が不可能。
Solution: 常にSecureConfigServiceから読み取り、設定ダイアログで変更を管理します。
Anti-Patterns
code-behindにビジネスロジック
What: .xaml.csのイベントハンドラに保存/読み込みロジックを直接記述。
Why It's Wrong: 実行中のWPFウィンドウなしではテスト不可能。MVVM分離に違反。
Better Approach: [RelayCommand]とデータバインディングですべてのロジックをViewModelに委譲。
タイムアウトなしの単一HttpClient
What: SSE呼び出しにTimeoutを設定せずにnew HttpClient()を作成。
Why It's Wrong: デフォルトタイムアウト(100秒)は長時間ワークフローを中断。タイムアウトなしは無限ハング。
Better Approach: 操作タイプごとに明示的なタイムアウトを設定。プーリングにはIHttpClientFactoryを検討。
Quick Reference
実装チェックリスト
Resources
Changelog
バージョン 1.0.0 (2026-02-15)
- 初回リリース: 単一ワークフローDify API連携ガイド
- 6ステップワークフロー: 構造 → 設定 → クライアント → UI → DI → カスタマイズ
- CurrentUserスコープでのDPAPI暗号化
- リアルタイム進捗レポート付きSSEストリーミング
- CommunityToolkit.Mvvm統合