ASP.NET Core 8 MVC中用TailwindCSS实现输入框错误状态样式
问题描述
我正在基于垂直切片架构开发ASP.NET Core 8 MVC应用,当前登录功能的控制器和视图代码如下:
控制器代码
[AllowAnonymous] [Route("login")] public class LoginController(IMediator mediator) : Controller { [HttpGet] public async Task<IActionResult> Index() { var viewModel = await mediator.Send(new LoginQuery()); return View("/Features/Login/Login.cshtml", viewModel); } [HttpPost] public async Task<IActionResult> Login(LoginCommand command) { if (!ModelState.IsValid) { return View("/Features/Login/Login.cshtml"); } var viewModel = await mediator.Send(command); if (string.IsNullOrEmpty(viewModel.Error)) { return RedirectToAction("index", "dashboard"); } return View("/Features/Login/Login.cshtml"); } }
视图代码(Login.cshtml)
<form asp-route="login" method="post" class="space-y-6"> <div> <label asp-for="Email" class="block text-sm font-medium leading-6 text-gray-900"></label> <div class="mt-2"> <input asp-for="Email" class="block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> <span asp-validation-for="Email" class="mt-2 text-sm text-red-600"></span> </div> </div> <div> <label asp-for="Password" class="block text-sm font-medium leading-6 text-gray-900"></label> <div class="mt-2"> <input asp-for="Password" class="block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> <span asp-validation-for="Password" class="mt-2 text-sm text-red-600"></span> </div> </div> <div> <button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Sign in</button> </div> </form> @section scripts { <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script> }
当前验证效果

期望验证效果

期望效果的参考代码:
<div> <label for="email" class="block text-sm font-medium leading-6 text-gray-900">Email</label> <div class="relative mt-2 rounded-md shadow-sm"> <input type="email" name="email" id="email" class="block w-full rounded-md border-0 py-1.5 pr-10 text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-2 focus:ring-inset focus:ring-red-500 sm:text-sm sm:leading-6" placeholder="you@example.com" value="adamwathan" aria-invalid="true" aria-describedby="email-error"> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> <svg class="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" /> </svg> </div> </div> <p class="mt-2 text-sm text-red-600" id="email-error">Not a valid email address.</p> </div>
我知道用React/Vue等前端框架更容易实现,但现在必须用传统MVC模式,请问这种约束下,实现这类样式的最简单方式是什么?
解决方案
1. 利用MVC内置验证状态类
ASP.NET MVC会在验证失败时自动给输入框添加input-validation-error类,给提示元素添加field-validation-error类,我们可以基于这些类编写样式,同时结合视图结构调整来匹配期望效果。
2. 重构视图结构并添加错误图标
调整HTML结构,把输入框和错误图标放在同一个相对定位容器中,保留asp-for和asp-validation-for标签助手自动处理验证状态:
<form asp-route="login" method="post" class="space-y-6"> <!-- 邮箱输入组 --> <div> <label asp-for="Email" class="block text-sm font-medium leading-6 text-gray-900"></label> <div class="relative mt-2 rounded-md shadow-sm"> <input asp-for="Email" class="block w-full rounded-md border-0 py-1.5 pr-10 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> <!-- 默认隐藏的错误图标 --> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 hidden"> <svg class="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" /> </svg> </div> </div> <span asp-validation-for="Email" class="mt-2 text-sm text-red-600"></span> </div> <!-- 密码输入组 --> <div> <label asp-for="Password" class="block text-sm font-medium leading-6 text-gray-900"></label> <div class="relative mt-2 rounded-md shadow-sm"> <input asp-for="Password" class="block w-full rounded-md border-0 py-1.5 pr-10 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"/> <!-- 默认隐藏的错误图标 --> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 hidden"> <svg class="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" /> </svg> </div> </div> <span asp-validation-for="Password" class="mt-2 text-sm text-red-600"></span> </div> <div> <button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Sign in</button> </div> </form> @section scripts { <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script> <script> $(document).ready(function() { // 处理服务器端验证状态 syncValidationState(); // 监听客户端验证事件 $('form').on('invalid-form.validate', syncValidationState); // 输入框内容变化时恢复正常状态 $('input').on('input', function() { $(this).removeClass('text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-red-500'); $(this).siblings('div').addClass('hidden'); }); function syncValidationState() { $('.input-validation-error').each(function() { $(this).addClass('text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-red-500'); $(this).siblings('div').removeClass('hidden'); }); } }); </script> }
3. 优化控制器返回逻辑
当前验证失败时返回视图未传递模型,会丢失用户输入,修改Post方法:
[HttpPost] public async Task<IActionResult> Login(LoginCommand command) { if (!ModelState.IsValid) { // 传递验证失败的模型,保留用户输入 return View("/Features/Login/Login.cshtml", command); } var viewModel = await mediator.Send(command); if (string.IsNullOrEmpty(viewModel.Error)) { return RedirectToAction("index", "dashboard"); } // 将登录逻辑错误添加到ModelState ModelState.AddModelError(string.Empty, viewModel.Error); return View("/Features/Login/Login.cshtml", command); }
4. 补充全局样式(可选)
如果需要更稳定的样式覆盖,可添加以下CSS到全局样式文件:
/* 强制显示错误图标 */ .input-validation-error ~ div { display: flex !important; } /* 错误状态输入框样式优先级提升 */ .input-validation-error.text-red-900 { color: #dc2626 !important; box-shadow: 0 0 0 1px #fca5a5 inset !important; }
内容的提问来源于stack exchange,提问作者J86
相关产品推荐
相关产品推荐

