I have the situation that is the Notes
column inside of the table is NULL
. This data is imported automatically.
I have page where user open to edit that record including the Notes
field, but the Notes
field is required value when user submits the form. I know the validation for Notes
field can be down at the client-side using Javascript, but I like validate using ASP.NET MVC data annotations if possible.
Here is the model class (simplified):
public class DetailModel
{
// Some other properties here....
public string? Notes { get; set; }
}
From the controller, it calls the method shown here below and the edit page works fine:
public async Task<ActionResult?> Detail(string? id)
{
DetailModel? Detail = await _StoredProcedures.GetDetail(id);
return View(Detail);
}
public async Task<DetailModel?> GetDetail(string? id)
{
List<DetailModel> Info = await _context.Database.SqlQuery<DetailModel>($@"EXECUTE [dbo].[p_DetailView] @Method = 'Info', @RecID = {id}").ToListAsync();
return Info.FirstOrDefault();
}
Now if I try to validate using the data annotations as shown here, it will break:
public class DetailModel
{
// Some other properties here....
[Required]
public string? Notes { get; set; }
}
Error message:
SqlNullValueException: Data is Null. This method or property cannot be called on Null values.
Microsoft.Data.SqlClient.SqlBuffer.ThrowIfNull()
InvalidOperationException: An error occurred while reading a database value for property 'DetailModel.Notes'. The expected type was 'System.String' but the actual value was null.
Is there a way to overcome this? Given the initial data is NULL, but enforce on the form submit to require value using data annotations?
You may want to have a mapper, a DTO and a ViewModel. In this way your DTO, which you use to exchange data with database, may be:
public class DetailDto
{
public string? Notes { get; set; }
}
Your View Model, which you use for your form, may be:
public class DetailModel
{
[Required]
public string Notes { get; set; }
}
Then you can do mapping from one shape to another when you need:
DetailDto dto = await _StoredProcedures.GetDetail(id);
DetailModel detailModel = DtoMapper.Map(dto);
You can start with simple static mapper and later consider more advanced technics:
public static class DtoMapper
{
public static DetailModel Map(DetailDto dto)
{
return new DetailModel
{
Notes = dto.Notes;
};
}
public static DetailDto Map(DetailModel model)
{
return new DetailDto
{
Notes = model.Notes;
};
}
}
With this, when you receive DetailModel
from the form you can use the power of data annotations along with the aforementioned if (ModelState.IsValid)
. If the data passes the validation you can map it back from DetailModel
to DetailDto
, and save to database.