mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-22 17:47:53 +00:00
Move from ValidationErrorResult to HttpBadRequest, and support object-level errors too
This commit is contained in:
@@ -14,16 +14,19 @@ export class Validation {
|
|||||||
var errors = <ValidationErrorResult>response;
|
var errors = <ValidationErrorResult>response;
|
||||||
Object.keys(errors || {}).forEach(key => {
|
Object.keys(errors || {}).forEach(key => {
|
||||||
errors[key].forEach(errorMessage => {
|
errors[key].forEach(errorMessage => {
|
||||||
// This in particular is rough
|
// If there's a specific control for this key, then use it. Otherwise associate the error
|
||||||
if (!controlGroup.controls[key].errors) {
|
// with the whole control group.
|
||||||
(<any>controlGroup.controls[key])._errors = {};
|
var control = controlGroup.controls[key] || controlGroup;
|
||||||
|
|
||||||
|
// This is rough. Need to find out if there's a better way, or if this is even supported.
|
||||||
|
if (!control.errors) {
|
||||||
|
(<any>control)._errors = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
controlGroup.controls[key].errors[errorMessage] = true;
|
control.errors[errorMessage] = true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ValidationErrorResult {
|
export interface ValidationErrorResult {
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNet.Mvc;
|
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.SpaServices
|
|
||||||
{
|
|
||||||
public class ValidationErrorResult : ObjectResult {
|
|
||||||
public const int DefaultStatusCode = 400;
|
|
||||||
|
|
||||||
public ValidationErrorResult(ModelStateDictionary modelState, int errorStatusCode = DefaultStatusCode)
|
|
||||||
: base(CreateResultObject(modelState))
|
|
||||||
{
|
|
||||||
if (!modelState.IsValid) {
|
|
||||||
this.StatusCode = errorStatusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IDictionary<string, IEnumerable<string>> CreateResultObject(ModelStateDictionary modelState)
|
|
||||||
{
|
|
||||||
if (modelState.IsValid) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return modelState
|
|
||||||
.Where(m => m.Value.Errors.Any())
|
|
||||||
.ToDictionary(m => m.Key, m => m.Value.Errors.Select(me => me.ErrorMessage));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Linq;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Authorization;
|
using Microsoft.AspNet.Authorization;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
@@ -118,7 +120,7 @@ namespace MusicStore.Apis
|
|||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
// Return the model errors
|
// Return the model errors
|
||||||
return new ValidationErrorResult(ModelState);
|
return HttpBadRequest(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dbAlbum = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId);
|
var dbAlbum = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId);
|
||||||
@@ -165,7 +167,7 @@ namespace MusicStore.Apis
|
|||||||
}
|
}
|
||||||
|
|
||||||
[ModelMetadataType(typeof(Album))]
|
[ModelMetadataType(typeof(Album))]
|
||||||
public class AlbumChangeDto
|
public class AlbumChangeDto : IValidatableObject
|
||||||
{
|
{
|
||||||
public int GenreId { get; set; }
|
public int GenreId { get; set; }
|
||||||
|
|
||||||
@@ -176,6 +178,21 @@ namespace MusicStore.Apis
|
|||||||
public decimal Price { get; set; }
|
public decimal Price { get; set; }
|
||||||
|
|
||||||
public string AlbumArtUrl { get; set; }
|
public string AlbumArtUrl { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
// An example of object-level (i.e., multi-property) validation
|
||||||
|
if (this.GenreId == 13 /* Indie */) {
|
||||||
|
switch (SentimentAnalysis.GetSentiment(Title)) {
|
||||||
|
case SentimentAnalysis.SentimentResult.Positive:
|
||||||
|
yield return new ValidationResult("Sounds too positive. Indie music requires more ambiguity.");
|
||||||
|
break;
|
||||||
|
case SentimentAnalysis.SentimentResult.Negative:
|
||||||
|
yield return new ValidationResult("Sounds too negative. Indie music requires more ambiguity.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AlbumResultDto : AlbumChangeDto
|
public class AlbumResultDto : AlbumChangeDto
|
||||||
|
|||||||
38
samples/angular/MusicStore/Apis/Models/SentimentAnalysis.cs
Normal file
38
samples/angular/MusicStore/Apis/Models/SentimentAnalysis.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace MusicStore.Models
|
||||||
|
{
|
||||||
|
// Obviously this is not a serious sentiment analyser. It is only here to provide an amusing demonstration of cross-property
|
||||||
|
// validation in AlbumsApiController.
|
||||||
|
public static class SentimentAnalysis
|
||||||
|
{
|
||||||
|
private static string[] positiveSentimentWords = new[] { "happy", "fun", "joy", "love", "delight", "bunny", "bunnies", "asp.net" };
|
||||||
|
|
||||||
|
private static string[] negativeSentimentWords = new[] { "sad", "pain", "despair", "hate", "scorn", "death", "package management" };
|
||||||
|
|
||||||
|
public static SentimentResult GetSentiment(string text) {
|
||||||
|
var numPositiveWords = CountWordOccurrences(text, positiveSentimentWords);
|
||||||
|
var numNegativeWords = CountWordOccurrences(text, negativeSentimentWords);
|
||||||
|
if (numPositiveWords > numNegativeWords) {
|
||||||
|
return SentimentResult.Positive;
|
||||||
|
} else if (numNegativeWords > numPositiveWords) {
|
||||||
|
return SentimentResult.Negative;
|
||||||
|
} else {
|
||||||
|
return SentimentResult.Neutral;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CountWordOccurrences(string text, string[] words)
|
||||||
|
{
|
||||||
|
// Very simplistic matching technique for this sample. Not scalable and not really even correct.
|
||||||
|
return new Regex(string.Join("|", words), RegexOptions.IgnoreCase).Matches(text).Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SentimentResult {
|
||||||
|
Negative,
|
||||||
|
Neutral,
|
||||||
|
Positive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
<form-field>
|
<form-field>
|
||||||
<div *ng-if="changesSaved" class="alert alert-success"><b>Done!</b> Your changes were saved.</div>
|
<div *ng-if="changesSaved" class="alert alert-success"><b>Done!</b> Your changes were saved.</div>
|
||||||
|
<div *ng-for="#errorMessage of formErrors" class="alert alert-danger">{{ errorMessage }}</div>
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(originalAlbum)">Delete</button>
|
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(originalAlbum)">Delete</button>
|
||||||
<a class="btn btn-default" [router-link]="['/Admin/Albums']">Back to List</a>
|
<a class="btn btn-default" [router-link]="['/Admin/Albums']">Back to List</a>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class AlbumEdit {
|
|||||||
var albumId = this.originalAlbum.AlbumId;
|
var albumId = this.originalAlbum.AlbumId;
|
||||||
|
|
||||||
this._putJson(`/api/albums/${ albumId }/update`, this.form.value).subscribe(response => {
|
this._putJson(`/api/albums/${ albumId }/update`, this.form.value).subscribe(response => {
|
||||||
if (response.ok) {
|
if (response.status === 200) {
|
||||||
this.changesSaved = true;
|
this.changesSaved = true;
|
||||||
} else {
|
} else {
|
||||||
AspNet.Validation.showValidationErrors(response, this.form);
|
AspNet.Validation.showValidationErrors(response, this.form);
|
||||||
@@ -88,6 +88,10 @@ export class AlbumEdit {
|
|||||||
headers: new Headers({ 'Content-Type': 'application/json' })
|
headers: new Headers({ 'Content-Type': 'application/json' })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get formErrors() {
|
||||||
|
return Object.keys(this.form.errors || {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Figure out what type declaration is provided by Angular/RxJs and use that instead
|
// TODO: Figure out what type declaration is provided by Angular/RxJs and use that instead
|
||||||
|
|||||||
Reference in New Issue
Block a user