/
index.html
651 lines (486 loc) · 37.6 KB
/
index.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
<i id="abp-title" data-title="Introduction With AspNet MVC Web API EntityFramework and AngularJS"></i>
<ul class="download">
<li>Get the source code from the <a href="https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/SimpleTaskSystem">GitHub repository.</a></li>
</ul>
<p>
<img alt="Simple Task System Screenshot" src="simple-task-system-screenshot.png" />
<br />
A screenshot of the sample application.
</p>
<h2 class="abp-invisible">Contents</h2>
<ul class="abp-invisible">
<li><a href="#ArticleIntroduction">Introduction</a></li>
<li><a href="#ArticleApplication">Create the application from boilerplate template</a></li>
<li><a href="#ArticleCreateEntities">Create entities</a></li>
<li><a href="#ArticleDbContext">Create DbContext</a></li>
<li><a href="#ArticleDbMigrations">Create Database Migrations</a></li>
<li><a href="#ArticleDefineRepositories">Define repositories</a></li>
<li><a href="#ArticleImplementRepositories">Implement repositories</a></li>
<li><a href="#ArticleBuildAppServices">Build application services</a></li>
<li><a href="#ArticleBuildWebApiServices">Build Web API services</a></li>
<li><a href="#ArticleDevelopSpa">Develop the SPA</a></li>
<li><a href="#ArticleLocalization">Localization</a></li>
<li><a href="#ArticleUnitTest">Unit testing</a></li>
<li><a href="#ArticleSummary">Summary</a></li>
<li><a href="#ArticleHistory">Article history</a></li>
<li><a href="#ArticleReferences">References</a></li>
</ul>
<h2 id="ArticleIntroduction">Introduction</h2>
<p>In this article, I'll show you how to develop a <strong> Single-Page Web Application</strong> (SPA) from ground to up using the following tools:</p>
<ul>
<li><strong>ASP.NET MVC</strong> and <strong>ASP.NET Web API</strong> as web framework.</li>
<li><strong>AngularJS</strong> as SPA framework.</li>
<li><strong>EntityFramework</strong> as ORM (Object-Relational Mapping) framework</li>
<li><strong>Castle Windsor</strong> as Dependency Injection framework.</li>
<li><strong>Twitter Bootstrap</strong> as HTML/CSS framework.</li>
<li><strong>Log4Net</strong> for logging, <strong>AutoMapper</strong> for object-to-object mapping.</li>
<li>And <strong>ASP.NET Boilerplate</strong> as startup template and application framework.</li>
</ul>
<p><strong>ASP.NET Boilerplate</strong> [1] is an open source application framework that combines all of these frameworks and libraries to make you start easily to develop your application. It provides us an infrastructure to develop applications in <strong>best practices</strong>. It naturally supports <strong>Dependency Injection</strong>, <strong>Domain Driven Design </strong>and <strong>Layered Architecture</strong>. The sample application also implements <strong>validation</strong>, <strong> exception handling</strong>, <strong>localization</strong> and <strong> responsive design</strong>.</p>
<h2 id="ArticleApplication">Create the application from boilerplate template</h2>
<p>ASP.NET Boilerplate saves our time while starting a new application by providing <strong>templates</strong> that <strong>combines </strong>and <strong> configures </strong>best tools to build enterprise level web applications.</p>
<p>Let's go to <a href="http://www.aspnetboilerplate.com/Templates">aspnetboilerplate.com/Templates</a> to build our application from template...</p>
<p><img alt="Create template by ASP.NET Boilerplate" src="create-abp-template-2.png" style="width: 100%" /></p>
<p>Here, I selected <strong>ASP.NET MVC 5.x</strong>, <strong>SPA</strong> (Single Page Application) with <strong>AngularJS</strong> and <strong>EntityFramework</strong>. Also entered <strong>SimpleTaskSystem</strong> for my project name.
I didn't want to include authentication options to get the most simple project
template. It created and downloaded my solution.</p>
<p>There are five projects included in the solution. <strong>Core</strong> project for domain (business) layer, <strong>Application</strong> project for application layer, <strong>WebApi</strong> project to implement Web Api controllers, <strong> Web</strong> project for presentation layer and finally <strong> EntityFramework</strong> project for EntityFramework implementation.</p>
<p><strong>Note</strong>: If you download sample solution for this acticle, you will see 7 projects in the solution. I improved template to support NHibernate and Durandal also for same application. If you don't interest in NHibernate or Durandal, just ignore these 2 projects.</p>
<h2 id="ArticleCreateEntities">Create entities</h2>
<p>I'm creating a simple application to create <strong>tasks</strong> and assing these tasks to <strong>people</strong>. So, I need <strong>Task</strong> and <strong> Person</strong> entities.</p>
<p><strong>Task</strong> entity simply defines a Description, CreationTime and a State for a Task. It also has an optional reference to a Person (AssignedPerson):</p>
<pre lang="cs">
public class Task : Entity<long>
{
[ForeignKey("AssignedPersonId")]
public virtual Person AssignedPerson { get; set; }
public virtual int? AssignedPersonId { get; set; }
public virtual string Description { get; set; }
public virtual DateTime CreationTime { get; set; }
public virtual TaskState State { get; set; }
public Task()
{
CreationTime = DateTime.Now;
State = TaskState.Active;
}
}</pre>
<p><strong>Person</strong> entity is simpler and just defines Name of the person:</p>
<pre lang="cs">
public class Person : Entity
{
public virtual string Name { get; set; }
}</pre>
<p>ASP.NET Boilerplate provides <strong>Entity</strong> class that defines <strong>Id</strong> poperty. I derived entities from this Entity class. Task class has an Id of type <strong>long</strong> since I derived from Entity<long>. Person class has an Id of type <strong>int</strong>. Since int is the default primary key type, I did not specified it.</p>
<p>I defined entities in the <strong>Core</strong> project since Entities are parts of domain/business layer.</p>
<h2 id="ArticleDbContext">Create DbContext</h2>
<p>As you know, EntityFramework works with <strong>DbContext</strong> class. We should first define it. ASP.NET Boilerplate template creates a DbContext template for us. I just added <strong>IDbSet</strong>s for Task and Person. This is my DbContext class:</p>
<pre lang="cs">
public class SimpleTaskSystemDbContext : AbpDbContext
{
public virtual IDbSet<Task> Tasks { get; set; }
public virtual IDbSet<Person> People { get; set; }
public SimpleTaskSystemDbContext()
: base("Default")
{
}
public SimpleTaskSystemDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}</pre>
<p>It uses <strong>Default</strong> connection string in web.config. It's defined as shown below:</p>
<pre lang="xml">
<add name="Default" connectionString="Server=localhost; Database=<strong>SimpleTaskSystem</strong>; Trusted_Connection=True;" providerName="System.Data.SqlClient" /></pre>
<h2 id="ArticleDbMigrations">Create Database Migrations</h2>
<p>We'll use EntityFramework's Code First Migrations to create and maintain the database schema. ASP.NET Boilerplate template has enabled migrations by default and added a <strong>Configuration </strong> class as shown below:</p>
<pre lang="cs">
internalinternal sealed class Configuration : DbMigrationsConfiguration<SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext context)
{
context.People.AddOrUpdate(
p => p.Name,
new Person {Name = "Isaac Asimov"},
new Person {Name = "Thomas More"},
new Person {Name = "George Orwell"},
new Person {Name = "Douglas Adams"}
);
}
}</pre>
<p>In the Seed method, I added four people for initial data. Now, I'll create the <strong>initial migration</strong>. I opened Package Manager Console and typed the following command:</p>
<p><img alt="Visual studio Package manager console" height="110" src="pkm-console2.png" width="590" /></p>
<p><strong>Add-Migration "InitialCreate"</strong> command creates a class named InitialCreate as shown below:</p>
<pre lang="cs">
public partial class InitialCreate : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.StsPeople",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.StsTasks",
c => new
{
Id = c.Long(nullable: false, identity: true),
AssignedPersonId = c.Int(),
Description = c.String(),
CreationTime = c.DateTime(nullable: false),
State = c.Byte(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.StsPeople", t => t.AssignedPersonId)
.Index(t => t.AssignedPersonId);
}
public override void Down()
{
DropForeignKey("dbo.StsTasks", "AssignedPersonId", "dbo.StsPeople");
DropIndex("dbo.StsTasks", new[] { "AssignedPersonId" });
DropTable("dbo.StsTasks");
DropTable("dbo.StsPeople");
}
}</pre>
<p>We did create needed classes to create the database, but not created the database yet. To do it, I'll run the following command:</p>
<pre>
PM> Update-Database</pre>
<p>This command runs migrations, creates the database and populates the initial data for us:</p>
<p><img alt="Database created by EntityFramework Migrations" height="147" src="sql-db.png" width="207" /></p>
<p>When we change Entitiy classes, we can easily create new migration classes using Add-Migration command and update the database with Update-Database command. To learn more about database migrations, see <a href="http://msdn.microsoft.com/en-us/data/aa937723" target="_blank">entity framework</a>'s documentation.</p>
<h2 id="ArticleDefineRepositories">Define repositories</h2>
<p>In domain driven design, repositories used to implement database-specific codes. ASP.NET Boilerplate creates an automatic repository for each entity using generic <strong>IRepository</strong> interface. IRepository defines common methods for select, insert, update, delete and a few more:</p>
<p><img alt="IRepository interface" height="741" src="irepository-interface-v3.png" width="494" /></p>
<p>We can extend these repository upon our needs. I will extend it to create a Task repository. As I want to separate interface from implementation, I declare interfaces for repositories first. Here, is the Task repository interface:</p>
<pre lang="cs">
public interface ITaskRepository : IRepository<Task, long>
{
List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}</pre>
<p>It extends generic <strong>IRepository</strong> interface of ASP.NET Boilerplate. So, <strong>ITaskRepository</strong> inherently defines all these methods as default. It can also add it's own methods as I defined <strong>GetAllWithPeople(...)</strong>.</p>
<p>No need to create a repository for Person since default methods are enough for me. ASP.NET Boilerplate provides a way of injecting generic repositories without creating a repository class. We will see it in TaskAppService class in 'Build application services' section..</p>
<p>I defined repository interfaces in the <strong>Core</strong> project since they are parts of domain/business layer.</p>
<h2 id="ArticleImplementRepositories">Implement repositories</h2>
<p>We should implement the ITaskRepository interface defined above. I'm implementing repositories in <strong>EntityFramework</strong> project. Thus, domain layer becomes completely independent from EntityFramework.</p>
<p>When we created the project template, ASP.NET Boilerplate defined a generic base class for repositories in our project: <strong>SimpleTaskSystemRepositoryBase</strong>. It's a good practice to have such a base class since we can later add some common methods for our repositories. You can see definition of this class in the code. I just derive from it for <strong>TaskRepository</strong> implementation:</p>
<pre lang="cs">
public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
public TaskRepository(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
{
//In repository methods, we do not deal with create/dispose DB connections, DbContexes and transactions. ABP handles it.
var query = GetAll(); //GetAll() returns IQueryable<T>, so we can query over it.
//var query = Context.Tasks.AsQueryable(); //Alternatively, we can directly use EF's DbContext object.
//var query = Table.AsQueryable(); //Another alternative: We can directly use 'Table' property instead of 'Context.Tasks', they are identical.
//Add some Where conditions...
if (assignedPersonId.HasValue)
{
query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
}
if (state.HasValue)
{
query = query.Where(task => task.State == state);
}
return query
.OrderByDescending(task => task.CreationTime)
.Include(task => task.AssignedPerson) //Include assigned person in a single query
.ToList();
}
}</pre>
<p><strong>TaskRepository</strong> is derived from <strong>SimpleTaskSystemRepositoryBase</strong> and implements <strong> ITaskRepository</strong> we defined above.</p>
<p>GetAllWithPeople is our specific method to get tasks where AssignedPerson included (pre-fetched) and optionally filtered by some conditions. We can freely use Context (EF's DBContext) object and database in repositories. ASP.NET Boilerplate manages database connection, transaction, creating and disposing the DbContext for us (See <a href="http://www.aspnetboilerplate.com/Pages/Documents/Repositories"> documentation</a> for more information)</p>
<h2 id="ArticleBuildAppServices">Build application services</h2>
<p>Application services is used to separate presentation layer from domain layer by providing façade style methods. I defined application services in the <strong> Application</strong> assembly in the project. First, I define interface for task application service:</p>
<pre lang="cs">
public interface ITaskAppService : IApplicationService
{
GetTasksOutput GetTasks(GetTasksInput input);
void UpdateTask(UpdateTaskInput input);
void CreateTask(CreateTaskInput input);
}</pre>
<p><strong>ITaskAppService</strong> extends <strong>IApplicationService</strong>. Thus, ASP.NET Boilerplate automatically provides some features for this class (like dependency injection and validation). Now, let's implement ITaskAppService:</p>
<pre lang="cs">
public class TaskAppService : ApplicationService, ITaskAppService
{
//These members set in constructor using constructor injection.
private readonly ITaskRepository _taskRepository;
private readonly IRepository<Person> _personRepository;
/// <summary>
///In constructor, we can get needed classes/interfaces.
///They are sent here by dependency injection system automatically.
/// </summary>
public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository)
{
_taskRepository = taskRepository;
_personRepository = personRepository;
}
public GetTasksOutput GetTasks(GetTasksInput input)
{
//Called specific GetAllWithPeople method of task repository.
var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
//Used AutoMapper to automatically convert List<Task> to List<TaskDto>.
return new GetTasksOutput
{
Tasks = Mapper.Map<List<TaskDto>>(tasks)
};
}
public void UpdateTask(UpdateTaskInput input)
{
//We can use Logger, it's defined in ApplicationService base class.
Logger.Info("Updating a task for input: " + input);
//Retrieving a task entity with given id using standard Get method of repositories.
var task = _taskRepository.Get(input.TaskId);
//Updating changed properties of the retrieved task entity.
if (input.State.HasValue)
{
task.State = input.State.Value;
}
if (input.AssignedPersonId.HasValue)
{
task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
}
//We even do not call Update method of the repository.
//Because an application service method is a 'unit of work' scope as default.
//ABP automatically saves all changes when a 'unit of work' scope ends (without any exception).
}
public void CreateTask(CreateTaskInput input)
{
//We can use Logger, it's defined in ApplicationService class.
Logger.Info("Creating a task for input: " + input);
//Creating a new Task entity with given input's properties
var task = new Task { Description = input.Description };
if (input.AssignedPersonId.HasValue)
{
task.AssignedPersonId = input.AssignedPersonId.Value;
}
//Saving entity with standard Insert method of repositories.
_taskRepository.Insert(task);
}
}</pre>
<p><strong>TaskAppService</strong> uses repositories for database operations. It gets references in it's constructor via <strong>constructor injection</strong> pattern. ASP.NET Boilerplate naturally implements dependency injection, so we can use constructor injection or property injection freely (See more on dependency injection in ASP.NET Boilerplate <a href="http://www.aspnetboilerplate.com/Pages/Documents/Dependency-Injection"> documentation</a>).</p>
<p>Notice that we're using PersonRepository by injecting <strong>IRepository<Person></strong>. ASP.NET Boilerplate automatically creates repositories for our entities. If default methods of IRepository enough for us, we don't have to create repository classes.</p>
<p>Application service methods works with Data Transfer Objects (DTOs). It's a best practice and I definitely suggest to use this pattern. But you don't have to do it as long as you can deal with <a href="http://www.aspnetboilerplate.com/Pages/Documents/Data-Transfer-Objects"> problems</a> of exposing Entities to presentation layer.</p>
<p>In the <strong>GetTasks</strong> method, I used the <strong>GetAllWithPeople</strong> method that I implemented before. It returns a <strong>List<Task></strong> but I need to return a <strong>List<TaskDto></strong> to presentation layer. <a href="http://automapper.org/" target="_blank">AutoMapper</a> helps us here to automatically convert <strong>Task</strong> objects to <strong>TaskDto</strong> objects. GetTasksInput and GetTasksOutput are special DTOs defined for GetTasks method.</p>
<p>In the <strong>UpdateTask</strong> method, I retrieved the Task from database (using IRepository's <strong>Get</strong> method) and changed peoperties of the Task. Notice that I did not even called <strong>Update</strong> method of the repository. ASP.NET Boilerplate implements <a href="http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work"> UnitOfWork</a> pattern. So, all changes in an application service method are a unit of work (atomic) and applied to database at the end of the method automatically.</p>
<p>In the <strong>CreateTask</strong> method, I simply created a new Task and inserted to database using the IRepository's Insert method.</p>
<p>ASP.NET Boilerplate's <strong>ApplicationService</strong> class has some properties to make developing application services easier. For example, it defines a <strong>Logger</strong> property for logging. So, we derived TaskAppService from ApplicationService and used it's Logger property here. It's optional to derive from this class but required to implement IApplicationService (notice that ITaskAppService extends IApplicationService).</p>
<h3>Validation</h3>
<p>ASP.NET Boilerplate automatically validates inputs of application service methods. CreateTask method gets <strong>CreateTaskInput</strong> as parameter:</p>
<pre lang="cs">
public class CreateTaskInput
{
public int? AssignedPersonId { get; set; }
[Required]
public string Description { get; set; }
public override string ToString()
{
return string.Format("[CreateTaskInput > AssignedPersonId = {0}, Description = {1}]", AssignedPersonId, Description);
}
}</pre>
<p>Here, Description is marked as <strong>Required</strong>. You can use any <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx" target="_blank"> Data Annotation attributes</a> here. If you want to make some custom validation, you can implement <strong>ICustomValidate</strong> as I implemented in <strong>UpdateTaskInput</strong>:</p>
<pre lang="cs">
public class UpdateTaskInput : ICustomValidate
{
[Range(1, long.MaxValue)]
public long TaskId { get; set; }
public int? AssignedPersonId { get; set; }
public TaskState? State { get; set; }
public void AddValidationErrors(List<ValidationResult> results)
{
if (AssignedPersonId == null && State == null)
{
results.Add(new ValidationResult("Both of AssignedPersonId and State can not be null in order to update a Task!", new[] { "AssignedPersonId", "State" }));
}
}
public override string ToString()
{
return string.Format("[UpdateTask > TaskId = {0}, AssignedPersonId = {1}, State = {2}]", TaskId, AssignedPersonId, State);
}
}</pre>
<p><strong>AddValidationErrors</strong> method is the place you can write your custom validation code.</p>
<h3>Handling exceptions</h3>
<p>Note that we did not handled any exception. ASP.NET Boilerplate automatically handles exceptions, logs and returns an appropriate error message to the client. Also, in client side, handles these error messages and show to the user. Actually, this is true for ASP.NET MVC and Web API Controller actions. Since we will expose the TaskAppService using Web API, we don't need to handle exceptions. See <a href="http://www.aspnetboilerplate.com/Pages/Documents/Handling-Exceptions">exception handling</a> document for details.</p>
<h2 id="ArticleBuildWebApiServices">Build Web API services</h2>
<p>I want to expose my application services to remote clients. Thus, my AngularJS application can easily call these service methods using <strong>AJAX</strong>.</p>
<p>ASP.NET Boilerplate provides an <strong>automatic</strong> way of exposing application service methods as ASP.NET Web API. I just use <strong>DynamicApiControllerBuilder</strong> as shown below:</p>
<pre lang="cs">
DynamicApiControllerBuilder
.ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem")
.Build();</pre>
<p>For this example, ASP.NET Boilerplate finds all interfaces inherits IApplicationService in Application layer assembly and creates a web api controller for each application service class. There are alternative syntaxes for fine control. We'll see how to call these services via AJAX.</p>
<h2 id="ArticleDevelopSpa">Develop the SPA</h2>
<p>I'll implement a Single-Page Web Application as user interface of my project. <strong>AngularJS</strong> (by Google) is one (propably the top one) of the most used SPA frameworks.</p>
<p>ASP.NET Boilerplate provides a <strong>template</strong> that makes easy to start with AngularJS. The template has two pages (Home and About) with smooth transition between pages. Uses Twitter <strong>Bootstrap</strong> as HTML/CSS framework (thus, it's responsive). It's also localized into English and Turkish with ASP.NET Boilerplate's localization system (You can easly add other languages or remove one of them).</p>
<p>We first change route of the template. ASP.NET Boilerplate template uses AngularUI-Router, the de-facto standard router of AngularJS. It provides state based routing modal. We will have two views: <strong>task list</strong> and <strong>new task</strong>. So, we will change route definition in <strong>app.js</strong> as shown below:</p>
<pre lang="js">
app.config([
'$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('tasklist', {
url: '/',
templateUrl: '/App/Main/views/task/list.cshtml',
menu: 'TaskList' //Matches to name of 'TaskList' menu in SimpleTaskSystemNavigationProvider
})
.state('newtask', {
url: '/new',
templateUrl: '/App/Main/views/task/new.cshtml',
menu: 'NewTask' //Matches to name of 'NewTask' menu in SimpleTaskSystemNavigationProvider
});
}
]);</pre>
<p><strong>app.js</strong> is the main JavaScript file to configure and start our SPA. Notice that we're using <strong>cshtml files as views</strong>! Normally, html files are used as views in AngularJS. ASP.NET Boilerplate makes it possible to use cshtml files. Thus we will have the power of <strong>razor</strong> engine to generate HTML.</p>
<p>ASP.NET Boilerplate provides an infrastructure to create and show <strong> menu</strong>s in an application. It allows to define menu in C# and use same menu both in C# and JavaScript. See <strong>SimpleTaskSystemNavigationProvider</strong> class for creating menu and see <strong>header.js/header.cshtml</strong> for showing menu in the angular way.</p>
<p>First, I'm creating an Angular <strong>controller</strong> for the <strong>task list</strong> view:</p>
<pre lang="js">
(function() {
var app = angular.module('app');
var controllerId = 'sts.views.task.list';
app.controller(controllerId, [
'$scope', 'abp.services.tasksystem.task',
function($scope, taskService) {
var vm = this;
vm.localize = abp.localization.getSource('SimpleTaskSystem');
vm.tasks = [];
$scope.selectedTaskState = 0;
$scope.$watch('selectedTaskState', function(value) {
vm.refreshTasks();
});
vm.refreshTasks = function() {
abp.ui.setBusy( //Set whole page busy until getTasks complete
null,
taskService.getTasks({ //Call application service method directly from JavaScript
state: $scope.selectedTaskState > 0 ? $scope.selectedTaskState : null
}).success(function(data) {
vm.tasks = data.tasks;
})
);
};
vm.changeTaskState = function(task) {
var newState;
if (task.state == 1) {
newState = 2; //Completed
} else {
newState = 1; //Active
}
taskService.updateTask({
taskId: task.id,
state: newState
}).success(function() {
task.state = newState;
abp.notify.info(vm.localize('TaskUpdatedMessage'));
});
};
vm.getTaskCountText = function() {
return abp.utils.formatString(vm.localize('Xtasks'), vm.tasks.length);
};
}
]);
})();</pre>
<p>I defined name of the controller as '<strong>sts.views.task.list</strong>'. This my convention (for scalable code-base) but you can simply name it as 'ListController'. AngularJS also uses <strong>dependency injection</strong>. We're injecting '<strong>$scope</strong>' and '<strong>abp.services.tasksystem.task</strong>' here. First one is Angular's scope variable, second one is the automatically created JavaScript service proxy for ITaskAppService (we built it before in 'Build Web API services' section).</p>
<p>ASP.NET Boilerplate provides infrastructure to use same <a href="http://www.aspnetboilerplate.com/Pages/Documents/Localization"> localization</a> texts both in server and client (see it's documentation for details). </p>
<p><strong>vm.taks</strong> is the list of tasks that will be shown in the view. <strong>vm.refreshTasks</strong> method fills this array by getting tasks using <strong>taskService</strong>. It's called when <strong> selectedTaskState</strong> changes (observed using $scope.$watch).</p>
<p>As you see, calling an application service method is very easy and straightforward! This is a feature of ASP.NET Boilerplate. It generates Web API layer and JavaScript proxy layer that talks with this Web API layer. Thus, we are calling the application service method as calling a simple JavaScript method. It is completely integrated with AngularJS (uses Angular's $http service).</p>
<p>Let's see the view side of task list:</p>
<pre lang="html">
<div class="panel panel-default" <strong>ng-controller</strong>="sts.views.task.list as vm">
<div class="panel-heading" style="position: relative;">
<div class="row">
<!-- Title -->
<h3 class="panel-title col-xs-6">
<strong>@L("TaskList")</strong> - <span>{{vm.getTaskCountText()}}</span>
</h3>
<!-- Task state combobox -->
<div class="col-xs-6 text-right">
<select <strong>ng-model</strong>="selectedTaskState">
<option value="0">@L("AllTasks")</option>
<option value="1">@L("ActiveTasks")</option>
<option value="2">@L("CompletedTasks")</option>
</select>
</div>
</div>
</div>
<!-- Task list -->
<ul class="list-group" <strong>ng-repeat</strong>="task in vm.tasks">
<div class="list-group-item">
<span class="task-state-icon glyphicon" ng-click="vm.changeTaskState(task)" ng-class="{'glyphicon-minus': task.state == 1, 'glyphicon-ok': task.state == 2}"></span>
<span ng-class="{'task-description-active': task.state == 1, 'task-description-completed': task.state == 2 }">{{task.description}}</span>
<br />
<span ng-show="task.assignedPersonId > 0">
<span class="task-assignedto">{{task.assignedPersonName}}</span>
</span>
<span class="task-creationtime">{{task.creationTime}}</span>
</div>
</ul>
</div></pre>
<p><strong>ng-controller</strong> attribute (in the first line) binds the controller to the view. <strong>@L("TaskList")</strong> gets localized text for "task list" (works on server while rendering HTML). It's possible since this is a <strong>cshtml</strong> file.</p>
<p><strong>ng-model</strong> binds combobox and the JavaScript variable. When the variable changes, the combobox updated. When the combobox changes, the valiable is updated. This is two-way binding of AngularJS.</p>
<p><strong>ng-repeat</strong> is another 'directive' of Angular that is used to render same HTML for each value in an array. When the array changes (an item is added for example), it's automatically reflected to the view. This is another powerful feature of AngularJS.</p>
<p>Note: When you add a JavaScript file (for example, for the 'task list' controller), you should add it to your page. This can be done by adding it to Home\Index.cshtml in the template.</p>
<h2 id="ArticleLocalization">Localization</h2>
<p>ASP.NET Boilerplate provides a flexible and strong localization system. You can use XML files or Resource files as localization source. You can also define custom localization sources. See <a href="http://www.aspnetboilerplate.com/Pages/Documents/Localization"> documentation</a> for more. In this sample application, I used XML files (it's under Localization folder in web application):</p>
<pre lang="xml">
<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
<texts>
<text name="TaskSystem" value="Task System" />
<text name="TaskList" value="Task List" />
<text name="NewTask" value="New Task" />
<text name="Xtasks" value="{0} tasks" />
<text name="AllTasks" value="All tasks" />
<text name="ActiveTasks" value="Active tasks" />
<text name="CompletedTasks" value="Completed tasks" />
<text name="TaskDescription" value="Task description" />
<text name="EnterDescriptionHere" value="Task description" />
<text name="AssignTo" value="Assign to" />
<text name="SelectPerson" value="Select person" />
<text name="CreateTheTask" value="Create the task" />
<text name="TaskUpdatedMessage" value="Task has been successfully updated." />
<text name="TaskCreatedMessage" value="Task {0} has been created successfully." />
</texts>
</localizationDictionary>
</pre>
<h2 id="ArticleUnitTest">Unit testing</h2>
<p>ASP.NET Boilerplate is designed to be testable. I authored an article to show unit and integration testing for ABP based projects. See the article: <a href="http://www.codeproject.com/Articles/871786/Unit-testing-in-Csharp-using-xUnit-Entity-Framewor" data-abp-href="/Pages/Articles/Unit-Testing-with-Entity-Framework,-xUnit-Effort/index.html">Unit testing in C# using xUnit, Entity Framework, Effort and ASP.NET Boilerplate</a>.</p>
<h2 id="ArticleSourceCode">Source Code</h2>
<p>You can get the latest source code here <a href="https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/SimpleTaskSystem"> https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/SimpleTaskSystem</a></p>
<h2 id="ArticleSummary" class="abp-invisible">Summary</h2>
<p class="abp-invisible">In this article, I demonstrated how to develop an NLayered ASP.NET MVC web application with a SPA and responsive user interface. I used ASP.NET Boilerplate since it makes easy to develop such applications using best practices and saves our time. Use these links for moe:</p>
<ul class="abp-invisible">
<li>Official site and documentation: <a href="http://www.aspnetboilerplate.com/">aspnetboilerplate.com</a></li>
<li>Official forum: <a href="http://forum.aspnetboilerplate.com/">forum.aspnetboilerplate.com</a></li>
<li>GitHub repositories: <a href="https://github.com/aspnetboilerplate">github.com/aspnetboilerplate</a></li>
<li>Follow on twitter: <a href="https://twitter.com/aspboilerplate">@aspboilerplate</a></li>
<li>Enterprise startup template: <a href="http://aspnetzero.com/">aspnetzero.com</a></li>
</ul>
<h2 id="ArticleHistory" class="abp-invisible">Article history</h2>
<ul class="abp-invisible">
<li><strong>2018-02-18</strong>: Upgraded sample project and article for ABP
v3.4.</li>
<li><strong>2016-10-26</strong>: Upgraded sample project to ABP v1.0.</li>
<li><strong>2016-07-19</strong>: Updated article and sample project for ABP v0.10.</li>
<li><strong>2015-06-08</strong>: Updated article and sample project for ABP v0.6.3.1.</li>
<li><strong>2015-02-20</strong>: Added link to unit test article and updated the sample project</li>
<li><strong>2015-01-05</strong>: Updated sample project for ABP v0.5.</li>
<li><strong>2014-11-03</strong>: Updated article and sample project for ABP v0.4.1.</li>
<li><strong>2014-09-08</strong>: Updated article and sample project for ABP v0.3.2.</li>
<li><strong>2014-08-17</strong>: Updated sample project to ABP v0.3.1.2.</li>
<li><strong>2014-07-22</strong>: Updated sample project to ABP v0.3.0.1.</li>
<li><strong>2014-07-11</strong>: Added screenshot of 'Enable-Migrations' command.</li>
<li><strong>2014-07-08</strong>: Updated sample project and article.</li>
<li><strong>2014-07-01</strong>: First publish of the article.</li>
</ul>
<h2 id="ArticleReferences" class="abp-invisible">References</h2>
<p class="abp-invisible">[1] ASP.NET Boilerplate official website: <a href="http://www.aspnetboilerplate.com/">http://www.aspnetboilerplate.com</a></p>