0

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:

current look

What I actually want is:

desired look

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?

1 Answer 1

0

I first customized error messages in the model[Required(ErrorMessage = "My custom message")], and then used JavaScript to check the text content of the validation error element to dynamically add or remove SVG icons,Here is my example:

public class MyModel
{
   [Required(ErrorMessage = "My custom message")]
      
 public string MyProperty { get; set; }
}

View:

<form asp-action="Privacy" asp-controller="Home" class="space-y-4">
    <div class="form-group">
        <label asp-for="MyProperty" class="block text-gray-700"></label>
        <div class="relative mt-2 rounded-md shadow-sm">
            <div class="relative">
                <input asp-for="MyProperty" class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring focus:border-blue-300" />
                <span class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none input-icon"></span>
            </div>
            <span asp-validation-for="MyProperty" class="block text-red-600 mt-1 validation-error"></span>
        </div>
    </div>
    <input type="submit" value="Submit" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-700" />
</form>

@section Scripts {
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
   
    <script>
        function addValidationIcons() {
            $('.validation-error').each(function () {
                var inputContainer = $(this).siblings('.relative').find('.input-icon');

                if ($(this).text().trim() !== '') {
                    if (!inputContainer.find('svg').length) {
                        let svg = `
                        <svg width="16" height="16" fill="red" class="bi bi-exclamation-circle" viewBox="0 0 20 20">
            <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>`;
                        inputContainer.html(svg);
                    }
                } else {
                    inputContainer.find('svg').remove();
                }
            });
        }

        $(document).ready(function () {
            addValidationIcons();

            $('input').on('blur keyup', function () {
                $(this).valid();
                addValidationIcons();
            });
        });
    </script>
}

When my input box is empty: enter image description here

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.