Skip to content

Commit 8a148d8

Browse files
Move from ValidationErrorResult to HttpBadRequest, and support object-level errors too
1 parent bba3889 commit 8a148d8

File tree

6 files changed

+72
-39
lines changed

6 files changed

+72
-39
lines changed

Microsoft.AspNet.AngularServices/npm/src/Validation.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@ export class Validation {
1414
var errors = <ValidationErrorResult>response;
1515
Object.keys(errors || {}).forEach(key => {
1616
errors[key].forEach(errorMessage => {
17-
// This in particular is rough
18-
if (!controlGroup.controls[key].errors) {
19-
(<any>controlGroup.controls[key])._errors = {};
17+
// If there's a specific control for this key, then use it. Otherwise associate the error
18+
// with the whole control group.
19+
var control = controlGroup.controls[key] || controlGroup;
20+
21+
// This is rough. Need to find out if there's a better way, or if this is even supported.
22+
if (!control.errors) {
23+
(<any>control)._errors = {};
2024
}
2125

22-
controlGroup.controls[key].errors[errorMessage] = true;
26+
control.errors[errorMessage] = true;
2327
});
2428
});
2529
}
26-
2730
}
2831

2932
export interface ValidationErrorResult {

Microsoft.AspNet.SpaServices/ValidationErrorResult.cs

Lines changed: 0 additions & 30 deletions
This file was deleted.

samples/angular/MusicStore/Apis/AlbumsApiController.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Linq;
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using System.Threading.Tasks;
35
using Microsoft.AspNet.Authorization;
46
using Microsoft.AspNet.Mvc;
@@ -118,7 +120,7 @@ public async Task<ActionResult> UpdateAlbum(int albumId, [FromBody] AlbumChangeD
118120
if (!ModelState.IsValid)
119121
{
120122
// Return the model errors
121-
return new ValidationErrorResult(ModelState);
123+
return HttpBadRequest(ModelState);
122124
}
123125

124126
var dbAlbum = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId);
@@ -165,7 +167,7 @@ public async Task<ActionResult> DeleteAlbum(int albumId)
165167
}
166168

167169
[ModelMetadataType(typeof(Album))]
168-
public class AlbumChangeDto
170+
public class AlbumChangeDto : IValidatableObject
169171
{
170172
public int GenreId { get; set; }
171173

@@ -176,6 +178,21 @@ public class AlbumChangeDto
176178
public decimal Price { get; set; }
177179

178180
public string AlbumArtUrl { get; set; }
181+
182+
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
183+
{
184+
// An example of object-level (i.e., multi-property) validation
185+
if (this.GenreId == 13 /* Indie */) {
186+
switch (SentimentAnalysis.GetSentiment(Title)) {
187+
case SentimentAnalysis.SentimentResult.Positive:
188+
yield return new ValidationResult("Sounds too positive. Indie music requires more ambiguity.");
189+
break;
190+
case SentimentAnalysis.SentimentResult.Negative:
191+
yield return new ValidationResult("Sounds too negative. Indie music requires more ambiguity.");
192+
break;
193+
}
194+
}
195+
}
179196
}
180197

181198
public class AlbumResultDto : AlbumChangeDto
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Linq;
2+
using System.Text.RegularExpressions;
3+
4+
namespace MusicStore.Models
5+
{
6+
// Obviously this is not a serious sentiment analyser. It is only here to provide an amusing demonstration of cross-property
7+
// validation in AlbumsApiController.
8+
public static class SentimentAnalysis
9+
{
10+
private static string[] positiveSentimentWords = new[] { "happy", "fun", "joy", "love", "delight", "bunny", "bunnies", "asp.net" };
11+
12+
private static string[] negativeSentimentWords = new[] { "sad", "pain", "despair", "hate", "scorn", "death", "package management" };
13+
14+
public static SentimentResult GetSentiment(string text) {
15+
var numPositiveWords = CountWordOccurrences(text, positiveSentimentWords);
16+
var numNegativeWords = CountWordOccurrences(text, negativeSentimentWords);
17+
if (numPositiveWords > numNegativeWords) {
18+
return SentimentResult.Positive;
19+
} else if (numNegativeWords > numPositiveWords) {
20+
return SentimentResult.Negative;
21+
} else {
22+
return SentimentResult.Neutral;
23+
}
24+
}
25+
26+
private static int CountWordOccurrences(string text, string[] words)
27+
{
28+
// Very simplistic matching technique for this sample. Not scalable and not really even correct.
29+
return new Regex(string.Join("|", words), RegexOptions.IgnoreCase).Matches(text).Count;
30+
}
31+
32+
public enum SentimentResult {
33+
Negative,
34+
Neutral,
35+
Positive,
36+
}
37+
}
38+
}

samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ <h2>Album <small>Edit</small></h2>
3737

3838
<form-field>
3939
<div *ng-if="changesSaved" class="alert alert-success"><b>Done!</b> Your changes were saved.</div>
40+
<div *ng-for="#errorMessage of formErrors" class="alert alert-danger">{{ errorMessage }}</div>
4041
<button type="submit" class="btn btn-primary">Submit</button>
4142
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(originalAlbum)">Delete</button>
4243
<a class="btn btn-default" [router-link]="['/Admin/Albums']">Back to List</a>

samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export class AlbumEdit {
6969
var albumId = this.originalAlbum.AlbumId;
7070

7171
this._putJson(`/api/albums/${ albumId }/update`, this.form.value).subscribe(response => {
72-
if (response.ok) {
72+
if (response.status === 200) {
7373
this.changesSaved = true;
7474
} else {
7575
AspNet.Validation.showValidationErrors(response, this.form);
@@ -88,6 +88,10 @@ export class AlbumEdit {
8888
headers: new Headers({ 'Content-Type': 'application/json' })
8989
});
9090
}
91+
92+
public get formErrors() {
93+
return Object.keys(this.form.errors || {});
94+
}
9195
}
9296

9397
// TODO: Figure out what type declaration is provided by Angular/RxJs and use that instead

0 commit comments

Comments
 (0)