Guest User

Untitled

a guest
Oct 21st, 2018
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.62 KB | None | 0 0
  1. /// <summary>
  2. /// API Controller class for <see cref="CourseCatalog"/> data.
  3. /// </summary>
  4. [Route("api/v1/course/catalog/")]
  5. [ApiController]
  6. public sealed class CourseCatalogController : ControllerBase
  7. {
  8. /// <summary>
  9. /// The object responsible for executing commands and queries.
  10. /// </summary>
  11. private readonly Messenger messenger;
  12.  
  13. /// <summary>
  14. /// Initializes a new instance of the
  15. /// <see cref="CourseCatalogController"/> class with a
  16. /// <see cref="Messenger"/> object.
  17. /// </summary>
  18. /// <param name="messenger">
  19. /// The object responsible for executing commands and queries. Must not
  20. /// be <c>null</c>.
  21. /// </param>
  22. /// <exception cref="ArgumentNullException">
  23. /// The argument passed to parameter <see cref="messenger"/> is
  24. /// <c>null</c>.
  25. /// </exception>
  26. public CourseCatalogController(Messenger messenger)
  27. {
  28. this.messenger =
  29. messenger ??
  30. throw new ArgumentNullException(nameof(messenger));
  31. }
  32.  
  33. /// <summary>
  34. /// Gets a course catalog that includes only those courses of the
  35. /// language selected by the site visitor.
  36. /// </summary>
  37. /// <param name="languageOfCourse">
  38. /// The language of the courses to include in the course catalog.
  39. /// </param>
  40. /// <returns>
  41. /// Returns a JSON representation of the course catalog.
  42. /// </returns>
  43. [HttpGet("{languageOfCourse}")]
  44. public async Task<ActionResult<CourseCatalog>> GetAsync(
  45. [Required, FromRoute] string languageOfCourse)
  46. {
  47. return await
  48. this.GetCourseCatalogAsync(
  49. new CourseCatalogQuery(languageOfCourse))
  50. .ConfigureAwait(false);
  51. }
  52.  
  53. /// <summary>
  54. /// Gets a course catalog that includes only those courses of the
  55. /// language and of the subject selected by the site visitor.
  56. /// </summary>
  57. /// <param name="languageOfCourse">
  58. /// The language of the courses to include in the course catalog.
  59. /// </param>
  60. /// <param name="subjectOfCourse">
  61. /// The subject of the courses to include in the course catalog.
  62. /// </param>
  63. /// <returns>
  64. /// Returns a JSON representation of the course catalog.
  65. /// </returns>
  66. [HttpGet("{languageOfCourse}/{subjectOfCourse}")]
  67. public async Task<ActionResult<CourseCatalog>> GetAsync(
  68. [Required, FromRoute] string languageOfCourse,
  69. [Required, FromRoute] string subjectOfCourse)
  70. {
  71. return await
  72. this.GetCourseCatalogAsync(
  73. new CourseCatalogQuery(languageOfCourse, subjectOfCourse))
  74. .ConfigureAwait(false);
  75. }
  76.  
  77. /// <summary>
  78. /// Returns a JSON representation of the course catalog that is
  79. /// returned by the specified query.
  80. /// </summary>
  81. /// <param name="query">
  82. /// The query object that returns the course catalog for which a JSON
  83. /// representation is returned.
  84. /// </param>
  85. /// <returns>
  86. /// Returns a JSON representation of the course catalog that is
  87. /// returned by the specified query.
  88. /// </returns>
  89. private async Task<ActionResult<CourseCatalog>> GetCourseCatalogAsync(
  90. CourseCatalogQuery query)
  91. {
  92. return await this.messenger
  93. .Send(query)
  94. .ConfigureAwait(false);
  95. }
  96. }
  97.  
  98. /// <summary>
  99. /// Base class for an object that executes queries.
  100. /// </summary>
  101. /// <typeparam name="TQuery">
  102. /// The <see cref="Type"/> of query.
  103. /// </typeparam>
  104. /// <typeparam name="TResult">
  105. /// The <see cref="Type"/> of result returned by the query.
  106. /// </typeparam>
  107. public abstract class QueryExecutor<TQuery, TResult>
  108. where TQuery : Query<TResult>
  109. {
  110. /// <summary>
  111. /// Executes the specified query.
  112. /// </summary>
  113. /// <param name="query">
  114. /// The query to execute. Must not be <c>null</c>.
  115. /// </param>
  116. /// <returns>
  117. /// Returns the result of the query.
  118. /// </returns>
  119. /// <exception cref="ArgumentNullException">
  120. /// The argument passed to parameter <paramref name="query"/> is
  121. /// <c>null</c>.
  122. /// </exception>
  123. /// <exception cref="QueryExecutionException">
  124. /// An error has occurred during the execution of a query.
  125. /// </exception>
  126. public Task<TResult> ExecuteAsync(TQuery query)
  127. {
  128. if (query is null)
  129. throw new ArgumentNullException(paramName: nameof(query));
  130.  
  131. async Task<TResult> Execute()
  132. {
  133. try
  134. {
  135. return await this.ExecuteQueryAsync(query)
  136. .ConfigureAwait(continueOnCapturedContext: false);
  137. }
  138. catch (Exception e)
  139. {
  140. throw new QueryExecutionException(
  141. typeOfQueryExecutor: this.GetType(), innerException: e);
  142. }
  143. }
  144.  
  145. return Execute();
  146. }
  147.  
  148. /// <summary>
  149. /// When overridden in a derived class, executes the query.
  150. /// </summary>
  151. /// <param name="query">
  152. /// The query to execute. Guaranteed not <c>null</c>.
  153. /// </param>
  154. /// <returns>
  155. /// When overridden in a derived class, returns the result of the query.
  156. /// </returns>
  157. protected abstract Task<TResult> ExecuteQueryAsync(TQuery query);
  158. }
  159.  
  160. /// <summary>
  161. /// Interface for a class that executes queries that retrieve course catalogs.
  162. /// </summary>
  163. public abstract class CourseCatalogQueryExecutor
  164. : QueryExecutor<CourseCatalogQuery, CourseCatalog>
  165. {
  166. }
  167.  
  168. /// <summary>
  169. /// Query object that provides the parameters for retrieving a
  170. /// <see cref="CourseCatalog"/> from a data source. Objects of this type
  171. /// are immutable.
  172. /// </summary>
  173. public sealed class CourseCatalogQuery : Query<CourseCatalog>
  174. {
  175. /// <summary>
  176. /// Initializes a new instance of the <see cref="CourseCatalogQuery"/>
  177. /// class with a language.
  178. /// </summary>
  179. /// <param name="languageOfCourse">
  180. /// The language of the courses to include in the course catalog.
  181. /// Must not be <c>null</c>.
  182. /// </param>
  183. /// <exception cref="ArgumentNullException">
  184. /// The argument passed to parameter <paramref name="languageOfCourse"/>
  185. /// is <c>null</c>.
  186. /// </exception>
  187. public CourseCatalogQuery(string languageOfCourse)
  188. {
  189. this.LanguageOfCourse =
  190. languageOfCourse
  191. ?? throw new ArgumentNullException(nameof(languageOfCourse));
  192.  
  193. this.SubjectOfCourse = string.Empty;
  194. }
  195.  
  196. /// <summary>
  197. /// Initializes a new instance of the <see cref="CourseCatalogQuery"/>
  198. /// class with a language
  199. /// and a subject.
  200. /// </summary>
  201. /// <param name="languageOfCourse">
  202. /// The language of the courses to include in the course catalog. Must
  203. /// not be <c>null</c>.
  204. /// </param>
  205. /// <param name="subjectOfCourse">
  206. /// The subject of the courses to include in the course catalog. Must
  207. /// not <c>null</c>.
  208. /// </param>
  209. /// <exception cref="ArgumentNullException">
  210. /// The argument passed to parameter <paramref name="languageOfCourse"/>
  211. /// or parameter <paramref name="languageOfCourse"/> is <c>null</c>.
  212. /// </exception>
  213. public CourseCatalogQuery(string languageOfCourse, string subjectOfCourse)
  214. {
  215. this.LanguageOfCourse =
  216. languageOfCourse
  217. ?? throw new ArgumentNullException(nameof(languageOfCourse));
  218.  
  219. this.SubjectOfCourse =
  220. subjectOfCourse
  221. ?? throw new ArgumentNullException(nameof(subjectOfCourse));
  222. }
  223.  
  224. /// <summary>
  225. /// Gets language of the courses to include in the course catalog.
  226. /// Guaranteed not <c>null</c>.
  227. /// </summary>
  228. public string LanguageOfCourse { get; }
  229.  
  230. /// <summary>
  231. /// Gets the subject of the courses to include in the course catalog.
  232. /// Guaranteed not <c>null</c>.
  233. /// </summary>
  234. public string SubjectOfCourse { get; }
  235. }
  236.  
  237. /// <summary>
  238. /// Class for an object that executes queries that retrieve course
  239. /// catalogs from a SQL data store.
  240. /// </summary>
  241. public sealed class DapperCourseCatalogQueryExecutor
  242. : CourseCatalogQueryExecutor
  243. {
  244. /// <summary>
  245. /// The database connection through which data is retrieved.
  246. /// </summary>
  247. public readonly DbConnection database;
  248.  
  249. /// <summary>
  250. /// Initializes a new instance of the
  251. /// <see cref="DapperCourseCatalogQueryExecutor"/> class with a
  252. /// database connection.
  253. /// </summary>
  254. /// <param name="database">
  255. /// The database connection through which data is retrieved.
  256. /// </param>
  257. /// <exception cref="ArgumentNullException">
  258. /// The argument passed to parameter <paramref name="database"/> is
  259. /// <c>null</c>.
  260. /// </exception>
  261. public DapperCourseCatalogQueryExecutor(DbConnection database)
  262. {
  263. this.database =
  264. database
  265. ?? throw new ArgumentNullException(paramName: nameof(database));
  266. }
  267.  
  268. /// <summary>
  269. /// Executes the specified <see cref="CourseCatalogQuery"/> query.
  270. /// </summary>
  271. /// <param name="query">
  272. /// The query to execute. Guaranteed not <c>null</c>.
  273. /// </param>
  274. /// <returns>
  275. /// Returns a <see cref="CourseCatalogQuery"/>.
  276. /// </returns>
  277. protected override async Task<CourseCatalog> ExecuteQueryAsync(
  278. CourseCatalogQuery query)
  279. {
  280. var sql = this.ConstructSqlStatement(query.SubjectOfCourse);
  281.  
  282. var parameters = new {
  283. query.LanguageOfCourse,
  284. query.SubjectOfCourse
  285. };
  286. var data = await this.database
  287. .QueryMultipleAsync(sql, parameters)
  288. .ConfigureAwait(false);
  289.  
  290. var languageData = data.ReadAsync();
  291. var subjectData = data.ReadAsync();
  292. var courseData = data.ReadAsync();
  293.  
  294. await Task.WhenAll(languageData, subjectData, courseData)
  295. .ConfigureAwait(false);
  296.  
  297. return this.ConstructCourseCatalog(
  298. languageOfCatalog: query.LanguageOfCourse,
  299. languageData: languageData.Result,
  300. subjectData: subjectData.Result,
  301. courseData: courseData.Result);
  302. }
  303.  
  304. /// <summary>
  305. /// Contructs a <see cref="CourseCatalog"/> using the provided data.
  306. /// </summary>
  307. /// <param name="languageOfCatalog">
  308. /// The languange in which the course catalog is published.
  309. /// </param>
  310. /// <param name="languageData">
  311. /// This list of languages in which the course catalog may be viewed.
  312. /// </param>
  313. /// <param name="subjectData">
  314. /// The list of subjects for which there are courses available.
  315. /// </param>
  316. /// <param name="courseData">
  317. /// The list of courses that are available to site visitors.
  318. /// </param>
  319. /// <returns>
  320. /// Returns a <see cref="CourseCatalog"/>.
  321. /// </returns>
  322. private CourseCatalog ConstructCourseCatalog(
  323. string languageOfCatalog,
  324. IEnumerable<dynamic> languageData,
  325. IEnumerable<dynamic> subjectData,
  326. IEnumerable<dynamic> courseData)
  327. {
  328. var availableLanguages =
  329. from language in languageData
  330. select new Language(
  331. language.Name,
  332. language.DisplayName,
  333. language.Country);
  334.  
  335. var availableSubjects =
  336. from course in subjectData
  337. let language = new Language(
  338. course.CourseLanguageName,
  339. course.CourseLanguageDisplayName,
  340. course.CourseLanguageCountry)
  341. select new Subject(
  342. course.CourseSubjectId,
  343. course.CourseSubjectName, language);
  344.  
  345. var availableCourses =
  346. from course in courseData
  347. let language = new Language(
  348. course.CourseLanguageName,
  349. course.CourseLanguageDisplayName,
  350. course.CourseLanguageCountry)
  351. let subject = new Subject(
  352. course.CourseSubjectId,
  353. course.CourseSubjectName,
  354. language)
  355. select new Course(
  356. course.CourseId,
  357. course.CourseTitle,
  358. course.CourseDescription,
  359. course.CourseImage,
  360. language,
  361. subject);
  362.  
  363. return new CourseCatalog(
  364. languageOfCatalog,
  365. availableLanguages,
  366. availableSubjects,
  367. availableCourses);
  368. }
  369.  
  370. /// <summary>
  371. /// Forms the sql statements that retrieve from the database all data
  372. /// that is necessary to contruct
  373. /// a <see cref="CourseCatalog"/>.
  374. /// </summary>
  375. /// <param name="subjectOfCourse">
  376. /// The subject of the courses to include in the course catalog.
  377. /// </param>
  378. /// <returns>
  379. /// Returns the sql statements that retrieve from the database all data
  380. /// that is necessary to contruct
  381. /// a <see cref="CourseCatalog"/>.
  382. /// </returns>
  383. private string ConstructSqlStatement(string subjectOfCourse)
  384. {
  385. var parameter =
  386. string.IsNullOrWhiteSpace(subjectOfCourse)
  387. ? "[CourseSubjectId]"
  388. : "@SubjectOfCourse";
  389.  
  390. return $"{this.SqlStatementToRetrieveListOfAvailableLanguages}" +
  391. $"{this.SqlStatementToRetrieveDistintListOfSubjects}" +
  392. string.Format($"{SqlStatementToRetrieveTheListOfCourses}", parameter);
  393. }
  394.  
  395. private string SqlStatementToRetrieveListOfAvailableLanguages =>
  396. $@"SELECT [Name]
  397. ,[DisplayName]
  398. ,[Country]
  399. FROM [Common].[Languages];{Environment.NewLine}";
  400.  
  401. private string SqlStatementToRetrieveDistintListOfSubjects =>
  402. $@"SELECT DISTINCT
  403. [CourseLanguageName]
  404. ,[CourseLanguageDisplayName]
  405. ,[CourseLanguageCountry]
  406. ,[CourseSubjectId]
  407. ,[CourseSubjectName]
  408. FROM [SpacedReps].[ReadModel].[CourseCatalog]
  409. WHERE [CourseLanguageMoniker] = @LanguageOfCourse{Environment.NewLine}";
  410.  
  411. private string SqlStatementToRetrieveTheListOfCourses =>
  412. @"SELECT [CourseId]
  413. ,[CourseTitle]
  414. ,[CourseDescription]
  415. ,[CourseImage]
  416. ,[CourseLanguageName]
  417. ,[CourseLanguageDisplayName]
  418. ,[CourseLanguageCountry]
  419. ,[CourseSubjectId]
  420. ,[CourseSubjectName]
  421. FROM [SpacedReps].[ReadModel].[CourseCatalog]
  422. WHERE [CourseLanguageMoniker] = @LanguageOfCourse
  423. AND [CourseSubjectId] = {0}";
  424. }
  425.  
  426. /// <summary>
  427. /// Responsible for sending commands or queries to their corresponding
  428. /// executors.
  429. /// </summary>
  430. public sealed class Messenger
  431. {
  432. /// <summary>
  433. /// The object that provides custom services to this object. In this
  434. /// particular case, provides the service of dynamically creating
  435. /// instances of the command or query executors.
  436. /// </summary>
  437. private IServiceProvider provider;
  438.  
  439. /// <summary>
  440. /// Initializes a new instance of the <see cref="Messenger"/> class
  441. /// with a provider.
  442. /// </summary>
  443. /// <param name="provider">
  444. /// The object that provides custom services to this object. In this
  445. /// particular case, provides the service of dynamically creating
  446. /// instances of the command or query executors. Must not be <c>null</c>.
  447. /// </param>
  448. /// <exception cref="ArgumentNullException">
  449. /// The argument passed to parameter <paramref name="provider"/> is
  450. /// <c>null</c>.
  451. /// </exception>
  452. public Messenger(IServiceProvider provider)
  453. {
  454. this.provider =
  455. provider
  456. ?? throw new ArgumentNullException(paramName: nameof(provider));
  457. }
  458.  
  459. /// <summary>
  460. /// Sends the specified query to its executor where it is executed.
  461. /// </summary>
  462. /// <typeparam name="T">
  463. /// The <see cref="Type"/> of query.
  464. /// </typeparam>
  465. /// <param name="query">
  466. /// The query to execute. Must not be <c>null</c>.
  467. /// </param>
  468. /// <returns>
  469. /// Returns the result of the query.
  470. /// </returns>
  471. /// <exception cref="ArgumentNullException">
  472. /// The argument passed to parameter <paramref name="query"/> is
  473. /// <c>null</c>.
  474. /// </exception>
  475. /// <exception cref="QueryExecutionException">
  476. /// An error has occurred during the execution of a query.
  477. /// </exception>
  478. public async Task<T> Send<T>(Query<T> query)
  479. {
  480. if (query is null)
  481. throw new ArgumentNullException(nameof(query));
  482.  
  483. Type type = typeof(QueryExecutor<,>);
  484. Type[] typeArguments = { query.GetType(), typeof(T) };
  485. Type typeOfExecutor = type.MakeGenericType(typeArguments);
  486.  
  487. dynamic executor = this.provider.GetService(typeOfExecutor);
  488.  
  489. T result = await executor.ExecuteAsync((dynamic)query)
  490. .ConfigureAwait(false);
  491.  
  492. return result;
  493. }
  494. }
  495.  
  496. /// <summary>
  497. /// Configures dependency injection for the application. Use this method to register services
  498. /// with the IoC container. This method gets called by the runtime.
  499. /// </summary>
  500. /// <param name="services">
  501. /// The container object to which services are added, thereby making those services available
  502. /// for dependency injection.
  503. /// </param>
  504. public void ConfigureServices(IServiceCollection services)
  505. {
  506. services.AddMvc()
  507. .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
  508.  
  509. var y = new SqlConnectionStringBuilder();
  510. y.DataSource = @".SQLEXPRESS";
  511. y.InitialCatalog = "TheDatabase";
  512. y.IntegratedSecurity = true;
  513. y.ConnectTimeout = 3;
  514.  
  515. services.AddTransient<DbConnection, SqlConnection>(x => new SqlConnection(y.ConnectionString));
  516. services.AddTransient<QueryExecutor<CourseCatalogQuery, CourseCatalog>, DapperCourseCatalogQueryExecutor>();
  517. services.AddSingleton<Messenger>();
  518. }
Add Comment
Please, Sign In to add comment