-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWcfCourse.cs
More file actions
520 lines (429 loc) · 28.6 KB
/
Copy pathWcfCourse.cs
File metadata and controls
520 lines (429 loc) · 28.6 KB
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
// ASP.NET is a web application framework by Microsoft for building dynamic web sites, services, and APIs using .NET.
// WCF (Windows Communication Foundation) is a .NET framework for building service-oriented applications that communicate over various protocols like HTTP, TCP, and named pipes.
// Often, they are used together - WCF services can be hosted inside ASP.NET applications using svc files.
// An .svc file is similar in purpose to an .aspx file in ASP.NET — it's an endpoint handler used to host a WCF service in IIS or ASP.NET.
// It maps incoming HTTP requests to the corresponding WCF service class.
// Structure of a .svc file:
<%@ ServiceHost Language= "C#" Service="MyNamespace.MyService" %>
// ServiceHost directive tells ASP.NET to use WCF's HTTP handler.
// Service="..." specifies the fully qualified name of the service class to be instantiated.
// Now, let's write a simple WCF application.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DATA TRANSFER OBJECT
// CustomerModule/Dtos/CustomerDto.cs
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The class whose purpose is to contain customer-related data for transmission between different layers of your application.
using System;
namespace MichaelZuskinWcfCourse.CustomerModule.Dtos // is many real applications, the folder can be named Model or Domain rater than Dtos
{
public class CustomerDto
{
public int? customerId { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public string? updatedBy { get; set; }
public DateTime? updatedAt { get; set; }
}
}
// CustomerDto represents a structured format for customer information that can be safely transmitted over the network.
// You use DTOs like CustomerDto to decouple internal data structures from external consumers and control the shape and contents of the transferred data.
// It's serialization-friendly - all the properties are public and have simple data types, which makes the class easily serializable by WCF for SOAP or REST communication.
// The use of nullable types provides flexibility and helps handle partial CRUD operation, especially with fields auto-generated by the DB.
// This class will be used as:
// * An input parameter for INSERT and UPDATE service methods.
// * A return type from SELECT service methods.
// Notice the folders structure:
namespace MichaelZuskinWcfCourse.CustomerModule.Dtos
// A module is usually a screen which contains a few components (data areas). That corresponds to the terminology of Angular.
// Normally, each module has a dedicated folder (like our CustomerModule).
// Our module (the Customer Screen) could contain such components as Customers List (for search), Customer Form, Address etc.
// CustomerDto would be the main DTO for the Customers List (you need an array of them) and the Customer Form components.
// For other components, MichaelZuskinWcfCourse folder would contain other ...Dto classes such as CustomerAddressDto.
// This logic works also for the data types which you will learn next.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SERVICE CONTRACT
// CustomerModule/Contracts/ICustomerContract.cs
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// A contract interface defines operations exposed by the web service, i.e. the methods that clients can remotely invoke over the network when interacting with the service.
// It's the contract between the WCF service and the client (front end).
using System.Collections.Generic;
using System.ServiceModel;
using MichaelZuskinWcfCourse.CustomerModule.Dtos;
namespace MichaelZuskinWcfCourse.CustomerModule.Contracts
{
[ServiceContract]
public interface ICustomerContract
{
[OperationContract]
List<CustomerDto> SelCustomerList(string lastName); // SELECT the list of customers whose last name includes the provided search parameter
[OperationContract]
CustomerDto SelCustomer(int customerId); // SELECT a singde customer by its ID
[OperationContract]
CustomerDto InsCustomer(CustomerDto cst); // INSERT a customer
[OperationContract]
CustomerDto UpdCustomer(CustomerDto cst); // UPDATE a customer.
[OperationContract]
int DelCustomer(int customerId); // DELETE a customer.
}
}
// The [ServiceContract] attribute is required to mark an interface as a WCF service contract.
// It tells the WCF runtime that the interface defines operations that are available to clients.
// This attribute ensures the service is discoverable, and can be consumed by various clients regardless of platform.
// Each method within the interface that should be exposed to clients must be decorated with the [OperationContract] attribute.
// This attribute indicates that the method is part of the service contract and can be invoked by clients remotely.
// Methods without this attribute will not be exposed to the client, even if they are defined in the interface.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// THE DATABASE CONTROLLER
// CustomerModule/DbControllers/CustomerDbController.cs
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The class which directly calls stored procedures.
// There are many .Net libraries for working with databases. Our example uses Argon, but you could work with something else.
// In any case, this file gives you a clear idea of the DB endpoint of a WCF web service.
// Our class communicates with Oracle, so the stored procedures names are used with the package name via dot notation.
using System;
using System.Collections.Generic;
using Argon.Core;
using Argon.Core.DbCommandParameters;
using MichaelZuskinWcfCourse.CustomerModule.Contracts;
using MichaelZuskinWcfCourse.CustomerModule.Dtos;
namespace MichaelZuskinWcfCourse.CustomerModule.DbControllers
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class CustomerDbController : ICustomerContract
{
const string dbPackage = "pkg_customer";
private readonly IArgonDbProviderFactory _dbProviderFactory = new ArgonDbProviderFactory();
public List<CustomerDto> SelCustomerList(string lastName)
{
var procParams = new List<DbCommandParameter>
{
new DbCommandParameter("i_last_name", DbCommandParameterType.String, lastName, ParameterDirection.Input)
};
return ExecProc("sel_customer_list", procParams);
}
public CustomerDto SelCustomer(int customerId)
{
var procParams = new List<DbCommandParameter>
{
new DbCommandParameter("i_customer_id", DbCommandParameterType.Number, customerId, ParameterDirection.Input)
};
var res = ExecProc("sel_customer", procParams);
return res.Count > 0 ? res[0] : null;
}
public CustomerDto InsCustomer(CustomerDto cst)
{
var procParams = GenerateSaveParams(cst);
var res = ExecProc("ins_customer", procParams);
return res.Count > 0 ? res[0] : null;
}
public CustomerDto UpdCustomer(CustomerDto cst)
{
var procParams = GenerateSaveParams(cst);
var res = ExecProc("upd_customer", procParams);
return res.Count > 0 ? res[0] : null;
}
public int DelCustomer(int customerId)
{
var procParams = new List<DbCommandParameter>
{
new DbCommandParameter("i_customer_id", DbCommandParameterType.Number, customerId, ParameterDirection.Input),
};
_dbProviderFactory.GetDbProvider().ExecuteProc($"{this.dbPackage}.del_customer", procParams);
}
private List<DbCommandParameter> GenerateSaveParams(CustomerDto cst)
// An extract from InsCustomer and UpdCustomer which populates parameters for both the saving procs.
{
return new List<DbCommandParameter>
{
new DbCommandParameter("i_customer_id", DbCommandParameterType.Number, cst.customerId, ParameterDirection.Input),
new DbCommandParameter("i_first_name", DbCommandParameterType.String, cst.firstName, ParameterDirection.Input),
new DbCommandParameter("i_last_name", DbCommandParameterType.String, cst.lastName, ParameterDirection.Input)
};
}
private List<CustomerDto> ExecProc(string proc, List<DbCommandParameter> procParams)
// An extract from SelCustomerList, SelCustomer, InsCustomer and UpdCustomer which calls the stored proc.
{
procParams.Add(new DbCommandParameter("o_ref_cur", DbCommandParameterType.RefCursor, ParameterDirection.Output));
var ret = new List<CustomerDto>();
using (var reader = _dbProviderFactory.GetDbProvider().ExecuteProcReturnReader($"{this.dbPackage}.{proc}", procParams))
{
while (reader.Read())
{
var cst = new CustomerDto
{
customerId = reader.GetInt32("customer_id").Value,
firstName = reader.GetString("first_name"),
lastName = reader.GetString("last_name"),
updatedBy = reader.GetString("updated_by"),
updatedAt = reader.GetDateTime("updated_at").Value
};
ret.Add(cst);
}
}
return ret.ToArray();
}
}
}
// The purpose of the CustomerDbController class is to serve as the WCF service's backend implementation that connects the service contract to the database.
// It isolates all database operations behind a clean interface and hides the complexity of stored procedure calls, parameter management, and result parsing.
// By doing so, it allows the rest of the application to interact with customer data through simple method calls, without knowing anything about how or where the data is stored.
// This promotes separation of concerns: the service contract defines WHAT must be done, and this class defines HOW it is done at the database level.
// @@@ InstanceContextMode
// Notice this attribute in the beginning of the class:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
// It controls how service instances are created and managed.
// PerCall means that a new instance of your service class is created for every client request (i.e., each time a client calls a service method).
// After the method call completes, the instance is destroyed (garbage-collected).
// Since service instances do not retain state between calls, you need to persist state across calls using DB or caching in static variables.
// The characteristics of PerCall:
// * Thread-safe by default: Since each call gets a new instance, there's no shared state to synchronize.
// * Highly scalable: Stateless instances allow easy scaling in load-balanced environments (e.g., cloud deployments).
// * Resource Efficiency: Resources (e.g., database connections) are held only for the duration of a single call, reducing memory leaks and long-term resource consumption.
// * Works with or without sessions (e.g., WSHttpBinding with sessions enabled). Even if a client uses the same proxy for multiple calls, each call gets a new service instance.
// The InstanceContextMode enum has two more constants - PerSession and Single.
// PerSession:
// Behavior: A single service instance is created per client session. The same instance handles all requests from the same client.
// State: Stateful (retains client-specific state across calls).
// Use Case: Stateful workflows (e.g., shopping carts, multi-step processes).
// Lifetime: Instance lives until the session times out or closes.
// Single:
// Behavior: A single service instance handles all requests from all clients. The instance is created when the service host opens.
// State: Global state (shared across all clients).
// Use Case: Shared resources (e.g., global cache, hardware access).
// When to Use Which:
// Use PerCall for REST-like stateless services (RESTfull APIs).
// Use PerSession for traditional stateful workflows (e.g., user login sessions).
// Use Single sparingly for shared global state (e.g., real-time dashboards).
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// THE BASE API CONTROLLER (HTTP ENDPOINT)
// WebApi/Infrastructure/BaseApiController (inherited from ApiController)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @@@ What is an HTTP endpoint (aka Web API)?
// An endpoint defines where and how a WCF service can be accessed, telling clients "where to find the service and how to talk to it".
// Programmatically, an endpoint is a class through which the service exposes its contract.
// That makes the service accessible to HTTP consumers, while maintaining separation of concerns and leveraging the existing WCF contract for data access and manipulation.
// An endpoint has:
// * An address which is a URL specifying where the endpoint can be accessed. Can include simple parameters (such as an entity ID).
// * Data to transfer (not included in the URL). Usually, it's a DTO.
// It's optional. For example, data is passed for INSERT and UPDATE since it would be impractical and unsafe to pass it within the URL.
// But it's not passed for a DELETE or a single-entity SELECT, when the service needs only the ID, which is easy to include in the URL.
// @@@ ApiController
// ApiController is the class used to build HTTP endpoints.
// Strictly speaking, it's not part of WCF - it's provided by the ASP.NET Core framework (more precisely, ASP.NET Web API) which is used in our WCF application.
// Descendants of ApiController are designed to:
// * Handle incoming HTTP requests (GET, POST, PUT, DELETE).
// * Return data to the callers in formats like JSON or XML (today, it's usually JSON).
// Key features include:
// * Enforces Attribute Routing: Requires attribute routing, making routing definitions explicit and clear.
// * Binding Source Parameter Inference: Infers binding sources for action parameters, such as [FromBody] for complex types.
// * Automatic Model State Validation: Validates the model state and automatically returns a 400 Bad Request response if the model state is invalid.
// * Problem Details Responses: Provides detailed error responses conforming to the Problem Details standard (application/problem+json).
// Essentially, it serves as the heart of your application's API, directing and processing requests and ensuring the correct data is sent back to the front end.
// When creating APIs that should follow RESTful principles, using ApiController ensures that best practices and conventions are automatically applied.
// @@@ BaseApiController
// Your application must provide a base ApiController (usually named... BaseApiController!) to be inherited from by concrete business API controllers.
// The purpose of creating a BaseApiController is to establish a common foundation for all business API controllers.
// It's "base" application-wide. But, of course, it's not really the very base since it itself has an ancestor (ApiController).
// BaseApiController:
// * Separates application-specific concerns (BaseApiController) from framework concerns (ApiController).
// * Makes the codebase more maintainable and testable.
// * Allows for easier refactoring and updates to common functionality.
// Typical tasks of BaseApiController:
// * Authentication and authorization logic that applies to all controllers.
// * Security policies (checking user permissions).
// * Logging and auditing mechanisms.
// * Performance monitoring and metrics collection.
// * Common error handling and exception management.
// * Input validation patterns.
// * Response formatting and standardization.
// * Consistency:
// Ensures all controllers follow the same patterns and conventions.
// Provides a consistent API experience for consumers.
// * Maintainability:
// Makes it easier to modify behavior across all controllers from one place.
// Reduces code duplication by implementing shared logic once.
// Now I'll give you a hypothetical example of such a class.
// You don't need to study it in detail - just get the general idea.
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace MichaelZuskinWcfCourse.WebApi.Infrastructure // notice that it's not within CustomerModule - it's an app-wide class used by multiple modules
{
public class BaseApiController : ApiController // !!!!!!! inherited from ApiController !!!!!!!
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
ValidateServiceCallAuthorization();
LogServiceCall();
}
private void ValidateServiceCallAuthorization()
{
var user = this.ControllerContext.RequestContext.Principal;
var userName = user?.Identity?.Name;
var isAuthenticated = user?.Identity?.IsAuthenticated ?? false;
var userRoles = user?.Identity as System.Security.Claims.ClaimsIdentity;
var url = this.ControllerContext.Request.RequestUri.ToString();
var httpMethod = this.ControllerContext.Request.Method.Method;
var headers = this.ControllerContext.Request.Headers;
var routeData = this.ControllerContext.RouteData;
var controllerName = routeData.Values["controller"]?.ToString();
var actionName = routeData.Values["action"]?.ToString();
var clientIpAddress = this.ControllerContext.Request.GetClientIpAddress();
var isAuthorized = ... // implementation of authorization check would go here...
if (!isAuthorized)
{
var err = $"Service call not authorized. URL: {url}, User: {userName}, Controller: {controllerName}, Action: {actionName}, Method: {httpMethod}, IP: {clientIpAddress}";
throw new UnauthorizedAccessException(err);
}
}
private void LogServiceCall()
{
var user = this.ControllerContext.RequestContext.Principal;
var userName = user?.Identity?.Name;
var url = this.ControllerContext.Request.RequestUri.ToString();
var httpMethod = this.ControllerContext.Request.Method.Method;
var headers = this.ControllerContext.Request.Headers;
var requestContent = this.ControllerContext.Request.Content;
var routeData = this.ControllerContext.RouteData;
var controllerName = routeData.Values["controller"]?.ToString();
var actionName = routeData.Values["action"]?.ToString();
var clientIpAddress = this.ControllerContext.Request.GetClientIpAddress();
var userAgent = headers.UserAgent?.ToString();
var timestamp = System.DateTime.UtcNow;
var requestId = System.Guid.NewGuid();
// implementation of logging would go here...
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// THE CONCRETE BUSINESS API CONTROLLER (HTTP ENDPOINT)
// CustomerModule/ApiControllers/CustomerApiController (inherited from BaseApiController)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The next CustomerApiController class serves as the HTTP endpoint handler for your WCF service.
// It acts as a thin layer between HTTP and the customer domain logic (the DB),
// i.e. it parses incoming URLs and invokes respective methods of CustomerDbController:
// HTTP request from front end -> the API controller -> the DB controller -> sending to DB
// After the methods invocation, the API controller returns results to the client using IHttpActionResult with proper:
// Status codes (200 OK, 400 Bad Request, 404 Not Found, etc.).
// Serialized output (JSON/XML).
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Http;
using MichaelZuskinWcfCourse.CustomerModule.DbControllers; // for CustomerDbController
using MichaelZuskinWcfCourse.CustomerModule.Dtos; // for CustomerDto
using MichaelZuskinWcfCourse.WebApi.Infrastructure; // for BaseApiController
namespace MichaelZuskinWcfCourse.CustomerModule.ApiControllers
{
[RoutePrefix("customers")]
public class CustomerApiController : BaseApiController // !!!!!!! inherited from BaseApiController !!!!!!!
{
private readonly CustomerDbController _db;
public CustomerApiController(CustomerDbController db)
{
_db = db;
}
[HttpGet]
[Route("")]
public IHttpActionResult SelCustomerList(string lastName)
{
List<CustomerDto> result = _db.SelCustomerList(lastName);
return Ok(result);
}
[HttpGet]
[Route("{customerId:int}")]
public IHttpActionResult SelCustomer(int customerId)
{
CustomerDto result = _db.SelCustomer(customerId);
return Ok(result);
}
[HttpPost]
[Route("")]
public IHttpActionResult InsCustomer([FromBody] CustomerDto cst)
{
CustomerDto result = _db.InsCustomer(cst);
return Ok(result);
}
[HttpPut]
[Route("{customerId:int}")]
public IHttpActionResult UpdCustomer([FromBody] CustomerDto cst)
{
CustomerDto result = _db.UpdCustomer(cst);
return Ok(result);
}
[HttpDelete]
[Route("{customerId:int}")]
public IHttpActionResult DelCustomer(int customerId)
{
int result = _db.DelCustomer(customerId);
return Ok(result);
}
}
}
// The CustomerApiController class exposes customer-related operations over HTTP in a RESTful manner.
// It handles incoming HTTP requests, maps them to corresponding WCF service methods, and returns structured HTTP responses.
// Each public method in the controller corresponds to a specific HTTP verb (GET, POST, PUT, DELETE) and performs one of the CRUD operations on customer data.
// These methods use attributes [HttpGet], [HttpPost], [HttpPut] and [HttpDelete] to indicate the type of HTTP request they handle.
// Note using [HttpPost] for INSERT and [HttpPut] for UPDATE.
// The [Route] defines the specific URL pattern that triggers each method.
// The controller is marked with [RoutePrefix("customers")], which means all routes are prefixed with "customers".
// The controller relies on dependency injection to receive an instance of CustomerDbController, which it uses to delegate the actual business logic and data operations.
// @@@ Ok()
// The Ok() function is defined in the ApiController class, which is part of the System.Web.Http namespace in ASP.NET Web API.
// It's a protected method with a return type of IHttpActionResult.
// The purpose of Ok() is to create an HTTP response with status code 200 (OK) and optionally include a response body.
// It's used to indicate that a request was successful and return data to the client.
// The data is serialized using content negotiation, meaning it will be formatted as JSON, XML, or another supported type depending on the client's request headers.
// Internally, Ok() creates an instance of OkNegotiatedContentResult<T>, which instructs the Web API framework to return the appropriate response.
// You could ask: "Why do the methods always return success? Is this system so perfect that errors never happen?"
// In fact, besides ok(), there are some more function you can use:
// 400 Bad Request:
return BadRequest("Wrong input data");
// It could be used to validate parameters, but we leave that task to the last routine in the call chain - the stored procedure
// (it must validate its parameters anyway, so we don't duplicate that functionality).
// 404 Not Found:
return NotFound();
// Is not usually used. In this situation, we normally return an empty data structure - a DTO with blank fields or an array of zero elements.
// We don't want to involve the error mechanism - the front end must check the returned data and decide what to do if it's blank.
// 500 Internal Server Error:
return InternalServerError(exeption); // is handled automatically by ASP.NET, so we are exempted from repeating try/catch blocks in every methof of the controller.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INJECTING THE DB CONTROLLER INTO THE API CONTROLLER
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// As you've seen, an instance of CustomerDbController is injected into CustomerApiController via the constructor:
public CustomerApiController(CustomerDbController db)
{
_db = db;
}
// To make this happen, you have to take some steps.
// Since these vary significantly depending on the middle tier architecture you are using, we won't give an example,
// but typically this involves registering the object to inject in a configuration file.
// If you've started working in a WCF project, see how this is done by searching for the class name of the injected object in some existing module.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// An alternative architecture of your WFC application
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// In our example, there are two layers within the web service:
// CustomerApiController
// ↓
// CustomerDbController
// This assumes that all business logic is in stored procedures, so the middle tier performs purely transportation functions.
// This is the preferrable architecture - using stored procedures for BL is the most efficient way.
// A typical business routine must access DB tables multiple times. A stored procedure is doing that without producing extra trafic, which makes it unbeatable.
// However, some systems encode business logic on the .Net side (in whole or in part), so an additional layer between the API and DB controllers is required.
// A class, belonging to that layer, could be named following the pattern <Entity>Bl, so ours could be CustomerBl.
// It goes without saying that it should implement ICustomerContract.
// So, the architecture of our system would turn into the following:
// CustomerApiController
// ↓
// CustomerBl
// ↓
// CustomerDbController
// In this case, CustomerBl (not CustomerDbController) would be injected now into CustomerApiController, and CustomerDbController would be injected into CustomerBl.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Host XML File (customer.svc) - SOAP Endpoint
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Purpose: Declares the service implementation for IIS hosting.
<% --File: CustomerService.svc --%>
<%@ ServiceHost Language="C#"
Service="MichaelZuskinWcfCourse.Services.CustomerDbController"
Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>