業務システムの入力フォームで「保存ボタンを二度押ししてしまった」「Enterキーで意図せず送信された」といった事故に悩んでいませんか?ASP.NET MVC では Razor ビュー、jQuery、そしてサーバ側の ActionMethod
を組み合わせることで、堅牢な二重登録防止を実現できます。本記事では、フロントとサーバ双方の実装パターンを整理し、すぐに導入できるコード例と設計上の注意点を解説します。
C# MVCで二重登録が発生する典型ケース
業務システムにおける二重登録は、ユーザ操作やブラウザの仕様によって簡単に発生します。特に注文や申請のように一度きりの処理が求められる場面では致命的です。代表的な発生ケースは以下のとおりです。
- ボタンの二度押しや通信遅延による再送信
- Enterキーによる暗黙的な
<form>
送信 - 「戻る→再送信」やリロードでの再 POST
- 同じユーザが複数タブから送信
いずれも現場で頻発する問題であり、システム側の多層的な対策が不可欠です。
RazorビューでのEnter抑止と送信制御
まずは利用者の誤操作を防ぐために、フロント側での制御を導入しましょう。ASP.NET MVCのRazorビューとjQueryを利用すれば、Enterキーによる暗黙送信を抑止し、送信ボタンの連打を防げます。
ASP.NET MVC Razor + jQuery の例
@using (Html.BeginForm("Create", "Order", FormMethod.Post, new { id = "orderForm" }))
{
@Html.AntiForgeryToken()
@Html.TextBoxFor(m => m.Name, new { autocomplete = "off" })
<button type="submit" id="submitBtn">登録</button>
}
@section Scripts {
<script>
$(function () {
var inFlight = false;
// Enterキーによる暗黙submit抑止
$('#orderForm').on('keydown', function (e) {
if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA' && !e.isComposing) {
e.preventDefault();
}
});
// ボタン連打防止
$('#orderForm').on('submit', function () {
if (inFlight) return false;
inFlight = true;
$('#submitBtn').prop('disabled', true).text('送信中...');
});
});
</script>
}
この仕組みによって、入力途中のEnterキー誤送信や、登録ボタンの連打を防ぐことができます。
ただし注意点があります。
JavaScriptでボタンを無効化すると、状況によっては「無効のまま戻らない」ことがあります。
- サーバ側でバリデーションエラーが発生し、再度同じビューを返す場合
- 通信エラーやタイムアウトでレスポンスが返ってこない場合
- サーバ例外でエラーページに遷移した場合
このようなケースでは、送信済みフラグが解除されず、ユーザが再送信できなくなります。
これを防ぐために、以下の工夫を組み合わせます。
- サーバ側で再描画時にボタンを有効化する
- Ajax送信を使う場合は
always
/complete
で復帰させる
jQuery Ajaxの例
$.ajax({
url: '/Order/Create',
type: 'POST',
data: $('#orderForm').serialize()
}).done(function (data) {
// 成功処理
}).fail(function () {
alert('エラーが発生しました');
}).always(function () {
// 成功・失敗問わずボタンを復帰
$('#submitBtn').prop('disabled', false).text('登録');
});
場合によっては「一定時間経過後に自動で有効化する」仕組みを入れることもあります。実務では「誤送信防止」と「操作不能の回避」を両立する設計が大切です。
コントローラ側の二重登録防止パターン
フロントだけでなく、サーバ側でも二重送信を防ぐ仕組みを整える必要があります。ASP.NET MVCでは AntiForgeryToken
と ModelState
を利用した検証が基本です。
ASP.NET MVC コントローラの例
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(OrderModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// サービス経由で一意チェック & 登録処理
var created = _orderService.CreateIfNotExists(model);
if (!created)
{
ModelState.AddModelError("", "すでに登録されています。");
return View(model);
}
return RedirectToAction("Completed");
}
ここでは以下のポイントを押さえています。
[ValidateAntiForgeryToken]
で不正送信を遮断ModelState.IsValid
によるサーバ側バリデーション- サービス層で一意チェックを実施し、既存データは登録しない
さらに、登録後は PRG(Post/Redirect/Get)パターン を適用し、リロードによる再送信を防ぎます。
データベースと業務キーでの重複防止
最終的な砦となるのはデータベースです。アプリケーション側でチェックしていても、同時送信など競合状況では二重登録が発生する可能性があります。そのため、DBレベルでの制約を必ず導入します。
SQL Server のユニーク制約例
CREATE UNIQUE INDEX UX_Order_OrderNo ON Orders(OrderNo);
登録処理はトランザクション内で実行し、制約違反が起きた場合は例外をキャッチして「すでに登録されています」とユーザに返します。これにより、同時操作でもデータの整合性を保証できます。
実装事例:注文登録フォームのケース
具体例として「注文登録フォーム」を想定すると、以下のような多層防御が現実的です。
- フロント側(Razor + jQuery)
- Enterキーを抑止
- 送信ボタンを一度だけ押せる仕様
- 失敗時は必ずボタンを復帰
- サーバ側(ASP.NET MVC コントローラ)
[ValidateAntiForgeryToken]
を適用ModelState.IsValid
による検証- PRGパターンで再送信を防止
- DB側(SQL Server)
OrderNo
をユニークキーとして設定- 制約違反発生時に例外を補足し、エラーメッセージを返却
このように各層で防御を組み合わせることで、実運用でも安心できる仕組みを実現できます。
まとめ:C# MVCでの多層防御チェックリスト
最後に、二重登録とEnter誤送信を防ぐためのチェックリストを示します。
ビュー側
- [ ] Enterキーによる暗黙 submit を抑止
- [ ] 送信ボタンを disable にし、送信中を明示
- [ ] エラー時・通信失敗時にボタンを復帰させる処理を追加
コントローラ側
- [ ]
[ValidateAntiForgeryToken]
の適用 - [ ]
ModelState.IsValid
による検証 - [ ] PRGパターンでのリダイレクト
DB側
- [ ] 業務キーにユニーク制約を設定
- [ ] 制約違反時のエラーハンドリングを実装
C# MVCでは、Razorビューでの誤送信抑止、コントローラでの正規リクエスト検証、そしてデータベース制約による最終保証を組み合わせることが、二重登録防止の王道パターンです。まずはフロントの「一度きり送信」と、DBのユニーク制約から導入してみてください。
コメント