-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvalidating-data-transfer-objects.html
134 lines (118 loc) · 5.7 KB
/
validating-data-transfer-objects.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>Entities</title>
<link type="text/css" rel="stylesheet" href="bootstrap.min.css" />
</head>
<body>
<ul>
<li><a href="#DocIntroToValidation">Introduction to validation</a></li>
<li><a href="#DocUsingAnnotations">Using data annotations</a></li>
<li><a href="#DocCustomValidation">Custom validation</a></li>
<li><a href="#DocNormalization">Normalization</a></li>
</ul>
<h3 id="DocIntroToValidation">Introduction to validation</h3>
<p>Inputs of an application should be validated first. This input can be sent by
user or another application. In a web application, validation is usually
implemented twice: in client and in the server. Client-side validation is
implemented mostly for user experience. It's better to check a form first in the
client and show invalid fields to the user. But, server-side validation is more
critical and unavoidable.</p>
<p>Server side validation is generally implemented in
<a href="/Pages/Documents/Application-Services">application services</a>. An
application service method should first check (validate) input and then use it.
ASP.NET Boilerplate provides a good infrastructure to automatically validate
input of an application service method.</p>
<p>An application service method gets a
<a href="/Pages/Documents/Data-Transfer-Objects">DTO</a> (Data Transfer Object)
as input. ASP.NET Boilerplate has <strong>IValidate</strong> interface that can
be implemented by DTOs to automatically validate them. Since <strong>IInputDto</strong>
extends IValidate, you may only implement IInputDto for input DTOs to ensure
validation.</p>
<h3 id="DocUsingAnnotations">Using data annotations</h3>
<p>ASP.NET Boilerplate supports data annotation attributes. Assume that we're
developing a Task application service that is used to create a task and gets an
input as shown below:</p>
<pre lang="cs">public class CreateTaskInput : IInputDto
{
public int? AssignedPersonId { get; set; }
[Required]
public string Description { get; set; }
}</pre>
<p>Here, <strong>Description</strong> property is marked as <strong>Required</strong>.
AssignedPersonId is optional. There are
also many attributes (like MaxLength, MinLength, RegularExpression...) in
<strong>System.ComponentModel.DataAnnotations</strong> namespace. See Task
<a href="/Pages/Documents/Application-Services">application service</a>
implementation:</p>
<pre lang="cs">public class TaskAppService : ITaskAppService
{
private readonly ITaskRepository _taskRepository;
private readonly IPersonRepository _personRepository;
public TaskAppService(ITaskRepository taskRepository, IPersonRepository personRepository)
{
_taskRepository = taskRepository;
_personRepository = personRepository;
}
public void CreateTask(CreateTaskInput input)
{
var task = new Task { Description = input.Description };
if (input.AssignedPersonId.HasValue)
{
task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
}
_taskRepository.Insert(task);
}
}</pre>
<p>As you see, no validation code is written since ASP.NET Boilerplate does it
automatically. ASP.NET Boilerplate also checks if
input is <strong>null</strong>. Throws <strong>AbpValidationException</strong> if so.
So, you don't have to write <strong>null-check</strong> code (guard clause). It
also throws
same exception if any of the input properties are invalid.</p>
<p>This machanism is similar to ASP.NET MVC's validation but notice that an
application service class is not derived from Controller, it's a plain class and
can work out of a web application.</p>
<h3 id="DocCustomValidation">Custom validation</h3>
<p>If data annotations are not sufficient for your case, you can implement
<strong>ICustomValidate</strong> interface as shown below:</p>
<pre lang="cs">public class CreateTaskInput : IInputDto, ICustomValidate
{
public int? AssignedPersonId { get; set; }
public bool SendEmailToAssignedPerson { get; set; }
[Required]
public string Description { get; set; }
public void AddValidationErrors(List<ValidationResult> results)
{
if (SendEmailToAssignedPerson && (!AssignedPersonId.HasValue || AssignedPersonId.Value <= 0))
{
results.Add(new ValidationResult("AssignedPersonId must be set if SendEmailToAssignedPerson is true!"));
}
}
}</pre>
<p>ICustomValidate interface declares <strong>AddValidationErrors</strong>
method to be implemented. Here, we have a <strong>SendEmailToAssignedPerson</strong> property. If it's
true, <strong>AssignedPersonId</strong> should be supplied, else it can be null.
We must add <strong>ValidationResult</strong> objects to <strong>results</strong> list if there are
validation errors.</p>
<h3 id="DocNormalization">Normalization</h3>
<p>We may need to perform an extra operation to arrange DTO parameters
after validation. ASP.NET Boilerplate defines <strong>IShouldNormalize</strong>
interface that has <strong>Normalize</strong> method for that. If you implement
this interface, Normalize method is called just after validation (before method
call). Assume that our DTO gets a Sorting direction. If it's not supplied, we
want to set a default sorting:</p>
<pre lang="cs">public class GetTasksInput : IInputDto, IShouldNormalize
{
public string Sorting { get; set; }
public void Normalize()
{
if (string.IsNullOrWhiteSpace(Sorting))
{
Sorting = "Name ASC";
}
}
}</pre>
</body>
</html>