In Asp.Net Core 2.0 e EF Core, come posso sovrascrivere gli attributi di convalida per una proprietà? Se aggiungo l'annotazione [Required] alla proprietà, accadono due cose:
ModelState
segnala questa proprietà come non valida (e quindi ModelState.IsValid
restituisce false
) -> Cattivo. Questa proprietà è esclusa dal modulo in HTML, poiché sto aggiungendo solo il valore FK nel controller. Ho un modello con una proprietà richiesta "Autore" che esegue la mappatura su IdentityUser; nel database, quella colonna deve essere non nullo, ma non voglio passare l'id utente tra il client e il server. Invece, quando viene ricevuto un POST, prendo l'userid dal contesto e imposto una proprietà FK sul modello.
Quindi, il mio risultato desiderato è un'annotazione di dati che specifica che la proprietà è richiesta per scopi di database ma facoltativa / saltata per scopi di validazione per quanto riguarda ModelState.
Il modello
public class ArticleModel
{
[Required]
public string Title {get;set}
[Required]
public string Body {get;set;}
[Required]
public IdentityUser Author { get; set; }
[ForeignKey("Author")]
public string AuthorId { get; set; }
}
The View (The view non conosce o cura dell'autore)
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Body" class="control-label"></label>
<input asp-for="Body" class="form-control" />
<span asp-validation-for="Body" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
Il metodo di creazione del controller
(ModelState.IsValid è sempre falso, a causa dello stato non valido dell'autore)
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public IActionResult Create([Bind("Body,Title")] ArticleModel articleModel)
{
articleModel.AuthorId = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (ModelState.IsValid)
{
DoStuff();
return RedirectToAction(nameof(Index));
}
return View(articleModel);
}
One Fix (brutto, fragile e ripetuto su diversi gestori)
controlla che tutti i campi desiderati siano validi, e ignora il resto ... sostituisci
if (ModelState.IsValid)
con
if (ModelState.Where(k => k.Key != "Author").All(v => v.Value.ValidationState == ModelValidationState.Valid))
Una migliore (?) Correzione (corretta - con DTO)
Stavo provando a usare l'impalcatura di default di MS qui, che prende un modello e lo alimenta semplicemente alle viste. In passato, in genere l'ho fatto manualmente con DTO, in cui tutto questo sarebbe stato un problema. È ancora il percorso migliore nell'era Asp.Net Core?
La correzione che pensavo di trovare
nel modello ....
[Required]
[Validation(Mode=ValidationModes.Manual)]
public IdentityUser Author {get;set;}
C'è un certo numero di problemi con l'utilizzo delle classi di entità direttamente per associare a POST e così via. Una vista che richiede regole di convalida diverse è ovviamente una di queste, ma devi anche occuparti di overposting, potenziali conflitti di contesto, ecc. Conflaccare la classe di entità con un "modello" è un peccato originale di ASP.NET MVC e, sfortunatamente, Microsoft non ha fatto un lavoro migliore nel dissuadere questa conflazione con ASP.NET Core. Semplicemente, una classe di entità non è un modello. Una classe di entità ha uno scopo semplice, che rappresenta una riga della tabella del database come un oggetto. Periodo. Usarlo per qualcosa di diverso invariabilmente causerà problemi.
Lunghi e brevi, sì, dovresti utilizzare i modelli di visualizzazione, i DTO, ecc. Qualunque cosa tu li chiami, sono essenzialmente la stessa cosa. Stai creando una classe allo scopo di una visualizzazione o di un ritorno da un metodo particolare, al fine di separare la tua rappresentazione del database da quella. La classe dell'entità deve contenere solo la logica necessaria per il proprio database. Il tuo modello di vista / DTO dovrebbe contenere solo la logica necessaria per ciò che stanno alimentando. Quindi, si mappa l'uno con l'altro. Non solo questo risolve il tuo problema qui, e anche altri problemi, ma serve anche a rimuovere le dipendenze dal tuo codice, che è sempre una buona cosa.