I am working on an ASP.NET Core 8 MVC application following the Vertical Slice architecture.
Here’s my controller code:
[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");
}
}
Here’s the markup from my Login.cshtml
view file:
<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>
}
When the model is invalid (e.g. I submit the form without providing an email address), right now I see this:
What I actually want is:
Here’s the code for the desired look:
<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="[email protected]" 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>
Note the SVG icon and a bunch of custom styling for the error state. I know this would be much easier if I was using a frontend framework like React/Vue …etc. But I’m using traditional MVC here.
Given my constraints, what would be the easiest way of achieving that sort of styling?