For one of my hobby projects, a server-side rendered React.js app using nextjs with asp.net core 2.2 serving the web API, I’ve made a conscious decision to keep all validation server-side only to reduce the effort required on the front-end side.
As for SPA being used for a hobby project, it’s great for learning, but not so much for speedy development… when will I ever learn – but that’s a story for another time.
ASP.NET Core Side – Camel Case Serialisation
One of the first things that I do when setting up ASP.NET Core Web API is setup serialisisation for JSON resonse to property names to be camel case – why oh why is this not the default anyway?
We need to install the package Microsoft.Extensions.Configuration.Json
.
Then change the actual serialiser configuration.
public class Startup
{
// ... others
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() );
// other configuration stuff...
return ConfigureIoC(services);
}
}
Setting up our API
For setting up an API, we need a Request DTO, in this example, a login request with some approriate rules.
public class LoginRequest
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
Then a pretty standard looking controller with check for model state validation.
The most important part is the last part, where we turn it into a serialisable Dictionary.
[Route("api/v1/account")]
[AllowAnonymous]
public class ApiController : Controller
{
[Route("login")]
[Post]
public async Task<IActionResult>Login([FromBody] LoginRequest login)
{
if (ModelState.IsValid)
{
// ... logic
}
return BadRequest(ModelState.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
));
}
}
Given an empty request body, with Content-Type: application/json
in the request header, we should receive the response:
{
"email": [
"The Email field is required.",
"The Email field is not a valid e-mail address."
],
"password": [
"The Password field is required."
]
}
React.js Inline Error Component
Now, for our React app, we simply create a component that takes in the field name, and the errors dictionary and simply list all the errors for that particular field – simple stuff.
import * as React from 'react'
export const InlineError = ({ field, errors }) => {
if(!errors) {
return null
}
if(!errors[field]) {
return null
}
return (<div className='errors-container'>
<ul>
{errors[field].map(error => <li>{error}</li>)}
</ul>
</div>)
}
We now have everything needed for displaying inline errors for our React forms.
<form>
<InlineError field='' errors={this.state.errors} />
<div className='form-group'>
<label>Email Address</label>
<input
type='text'
onChange={(evt) => this.setState({ email: evt.target.value })}
/>
<InlineError field='email' errors={this.state.errors} />
</div>
<div className='form-group'>
<label>Password</label>
<input
type='password'
onChange={(evt) => this.setState({ password: evt.target.value })}
/>
<InlineError field='password' errors={this.state.errors} />
</div>
<div className='form-group'>
<button
className='save-btn'
onClick={this.onLoginClick.bind(this)}
>Login
</button>
</div>
</form>
This is the end result – not pretty, but it does the job and it took very little effort – speed > fancy stuff.