.NET Core 3.0 introduces a new type of console application called Worker Service. I hope you are not confused with the term “Service Worker” that deals with offline web applications in the front-end world. In case you’re wondering what is it, you can refer to my other post to have a brief overview and this article for an in-depth understanding. This post introduces one way of building a scheduled job in .NET Core using Quartz.
Table of contents:
- Assumed knowledge
- Use case
- What about the other approaches?
- Application design
- Scaffolding the project
- NuGet libraries
- The Domain Layer
- The Data Layer
- The Application Layer (Worker Service)
Assumed knowledge
- C#, .NET Core
- Cron expression
- Dependency injection
- Visual Studio
Use case
In traditional console .NET applications, we normally implement schedule jobs using Task Scheduler available on Windows OS. Typically, you have an executable file and you use Task Schedule to trigger its execution. You could do the same thing with Worker Service but I just wanted to show you the alternative. This is great for building long running tasks for containers running in a Kubernetes cluster. The requirement for this job is to pull sales items from an on-prem database and push them to Dynamics D365 CRM via its OData endpoint. To learn how you can obtain a token for the API, please refer to my post talking about access token for CRM in Azure.
What about the other approaches?
Schedule jobs on windows can be implemented in one of the following ways:
- Using Task Scheduler available on windows to invoke an execution
- Using a trigger web job built with WebJob SDK
- Using Quartz.NET
- Using a SQL agent to trigger a SSIS package
- Using a job pod running inside a Kubernetes node (windows os)
Application design
We will be using Quartz to schedule our job, two repositories to deal with command/query operations in the data access layer – the first one is for querying data from the database, the second one is for inserting data into Dynamics CRM. We’ll then inject these repositories to our Quartz job. The application is divided into 3 layers:
- Data access layer (.NET Standard 2.1): this is where our repositories live, any commands, queries should go here.
- Domain layer (.NET Standard 2.1): this is where our domain models live.
- Application layer (.NET Core 3.1): this is where our Worker Service and Quartz reside
I won’t discuss all these in detail, rather I want to show how they all fit together.
If you don’t know much about the repository pattern, this post gives you an insight.
This application will be containerized and running in a Kubernetes cluster but for now, we’ll just stick to the basic.
Scaffolding the project
Let’s open up our Visual Studio and create the solution. I am using Visual Studio 2019 for this project because it has the built in Worker Service template as well as the .NET Core SDK 3.1 installed with it, however, you can also use the dotnet CLI.
<span class="token punctuation">-</span> DIT.IntegrationService
<span class="token punctuation">-</span> DIT.IntegrationService.Data
<span class="token punctuation">-</span> DIT.IntegrationService.Domain
<span class="token punctuation">-</span> DIT.IntegrationService.Worker
NuGet libraries
For the whole solution, I found these libraries are very useful:
- Quartz.NET for scheduling our job(s).
- Dapper is a lightweight ORM for doing raw SQL.
- Json.NET is a JSON Serializer.
- Error handling: C# functional extension. This library is an implementation of Railway Oriented Programming, very very useful for error handling.
- Structured Event Logging: Serilog.
- Centralized structured logs platform: SEQ.
The Domain Layer
This is where our domain models live. Usually I build this layer first out the the 3 because other layers rely on it. The models hold information about Stock Items retrieved from the local database and any related entities to create CRM products (such as product, product price level entity etc).
The Data Layer
As I mentioned earlier, I am going to have 2 repositories:
IStockItemRepository
: for querying items from the local database.ICrmRepository
: for inserting items to D365 CRM as products through its OData endpoint. This repository accepts an instance of HttpClient, a class we use to do our Http Verbs against CRM OData endpoint.
StockItemRepository<span class="token punctuation">.</span>cs
:
<span class="token keyword">using</span> CSharpFunctionalExtensions<span class="token punctuation">;</span>
<span class="token keyword">using</span> Dapper<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Domain<span class="token punctuation">;</span>
<span class="token keyword">using</span> Serilog<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Collections<span class="token punctuation">.</span>Generic<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Linq<span class="token punctuation">;</span>
<span class="token keyword">namespace</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Data
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IStockItemRepository</span>
<span class="token punctuation">{</span>
Result<span class="token operator"><</span>IEnumerable<span class="token operator">></span> <span class="token function">GetAllItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">StockItemRepository</span><span class="token punctuation">:</span> IStockItemRepository
<span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> SqlConnectionFactory _factory<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token function">StockItemRepository</span><span class="token punctuation">(</span><span class="token keyword">string</span> connection<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
_factory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SqlConnectionFactory</span><span class="token punctuation">(</span>connection<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> Result<span class="token operator"><</span>IEnumerable<span class="token operator">></span> <span class="token function">GetAllItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
IEnumerable items <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token keyword">string</span> query <span class="token operator">=</span> <span class="token string">@"SELECT StockCode
, RetailPriceInc
, ResellerPriceInc
FROM StockItems
ORDER BY StockCode"</span><span class="token punctuation">;</span>
<span class="token keyword">using</span> <span class="token punctuation">(</span><span class="token keyword">var</span> con <span class="token operator">=</span> _factory<span class="token punctuation">.</span><span class="token function">CreateOpenConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">try</span>
<span class="token punctuation">{</span>
items <span class="token operator">=</span> con<span class="token punctuation">.</span><span class="token function">Query</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span>
Log<span class="token punctuation">.</span><span class="token function">Debug</span><span class="token punctuation">(</span><span class="token string">"{StockItemCount} Stock Item retreived."</span><span class="token punctuation">,</span> items<span class="token punctuation">.</span><span class="token function">Count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">"Stock Item retreive failed. {ErrorMesg}"</span><span class="token punctuation">,</span> ex<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token generic-method function">Failure<span class="token punctuation"><</span>IEnumerable<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Ok</span><span class="token punctuation">(</span>items<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
CrmRepository<span class="token punctuation">.</span>cs
:
<span class="token keyword">using</span> CSharpFunctionalExtensions<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Converters<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Crm<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>OData<span class="token punctuation">;</span>
<span class="token keyword">using</span> Newtonsoft<span class="token punctuation">.</span>Json<span class="token punctuation">;</span>
<span class="token keyword">using</span> Serilog<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Collections<span class="token punctuation">.</span>Generic<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Linq<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>Http<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Text<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>RegularExpressions<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks<span class="token punctuation">;</span>
<span class="token keyword">namespace</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Data
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ICrmRepository</span>
<span class="token punctuation">{</span>
Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">GetAsync</span><span class="token punctuation">(</span>
<span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> Guid entiyId<span class="token punctuation">,</span> <span class="token keyword">params</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> cols<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity<span class="token punctuation">;</span>
Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">GetWithFetchXmlAsync</span><span class="token punctuation">(</span>
<span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> <span class="token keyword">string</span> fetchXml<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity<span class="token punctuation">;</span>
Task<span class="token operator"><</span>Result<span class="token operator"><</span>IEnumerable<span class="token operator">></span><span class="token operator">></span> <span class="token function">GetMultiple_WithFetchXmlAsync</span><span class="token punctuation">(</span>
<span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> <span class="token keyword">string</span> fetchXml<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity<span class="token punctuation">;</span>
Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">CreateAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> T item<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity<span class="token punctuation">;</span>
Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">UpdateAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> T updatedItem<span class="token punctuation">,</span> Guid entityId<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity<span class="token punctuation">;</span>
Task <span class="token function">DeleteAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> Guid entityId<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CrmRepository</span> <span class="token punctuation">:</span> ICrmRepository
<span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> HttpClient _httpClient<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token function">CrmRepository</span><span class="token punctuation">(</span>HttpClient httpClient<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> _httpClient <span class="token operator">=</span> httpClient<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">GetAsync</span><span class="token punctuation">(</span>
<span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> Guid entiyId<span class="token punctuation">,</span> <span class="token keyword">params</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> cols<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity
<span class="token punctuation">{</span>
<span class="token keyword">string</span> url <span class="token operator">=</span> $<span class="token string">"{_httpClient.BaseAddress.AbsoluteUri}/{entityPluralName}({entiyId})"</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>cols <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&&</span> cols<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> url <span class="token operator">=</span> $<span class="token string">"{url}?$select={string.Join("</span><span class="token punctuation">,</span><span class="token string">", cols)}"</span><span class="token punctuation">;</span>
HttpResponseMessage response <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> settings <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonSerializerSettings</span>
<span class="token punctuation">{</span>
Converters <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonConverter</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token class-name">JsonDynamicNameConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
Formatting <span class="token operator">=</span> Formatting<span class="token punctuation">.</span>None<span class="token punctuation">,</span>
DefaultValueHandling <span class="token operator">=</span> DefaultValueHandling<span class="token punctuation">.</span>Ignore
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">try</span>
<span class="token punctuation">{</span>
response <span class="token operator">=</span> <span class="token keyword">await</span> _httpClient<span class="token punctuation">.</span><span class="token function">SendAsync</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">HttpRequestMessage</span><span class="token punctuation">(</span>HttpMethod<span class="token punctuation">.</span>Get<span class="token punctuation">,</span> url<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">.</span>IsSuccessStatusCode<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> content <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span>Content<span class="token punctuation">.</span><span class="token function">ReadAsStringAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> entity <span class="token operator">=</span> JsonConvert<span class="token punctuation">.</span><span class="token function">DeserializeObject</span><span class="token punctuation">(</span>content<span class="token punctuation">,</span> settings<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Ok</span><span class="token punctuation">(</span>entity<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CrmHttpResponseException</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>Content<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span>
<span class="token string">"Failed to get D365 entity (Type= {@EntityType}). EntityId: {@EntityId}. Error: {@Exception}"</span><span class="token punctuation">,</span>
<span class="token function">nameof</span><span class="token punctuation">(</span>T<span class="token punctuation">)</span><span class="token punctuation">,</span> entiyId<span class="token punctuation">,</span> ex
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Failure</span><span class="token punctuation">(</span>ex<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">finally</span>
<span class="token punctuation">{</span>
response<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">GetWithFetchXmlAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> <span class="token keyword">string</span> fetchXml<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity
<span class="token punctuation">{</span>
<span class="token keyword">string</span> url <span class="token operator">=</span> @$<span class="token string">"{_httpClient.BaseAddress.AbsoluteUri}/{entityPluralName}?fetchXml={fetchXml}"</span><span class="token punctuation">;</span>
HttpResponseMessage response <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> settings <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonSerializerSettings</span>
<span class="token punctuation">{</span>
Converters <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonConverter</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token class-name">JsonDynamicNameConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
Formatting <span class="token operator">=</span> Formatting<span class="token punctuation">.</span>None<span class="token punctuation">,</span>
DefaultValueHandling <span class="token operator">=</span> DefaultValueHandling<span class="token punctuation">.</span>Ignore
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">try</span>
<span class="token punctuation">{</span>
response <span class="token operator">=</span> <span class="token keyword">await</span> _httpClient<span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">.</span>IsSuccessStatusCode<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> content <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span>Content<span class="token punctuation">.</span><span class="token function">ReadAsStringAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ODataResponse odataResp <span class="token operator">=</span> JsonConvert<span class="token punctuation">.</span><span class="token generic-method function">DeserializeObject<span class="token punctuation"><</span>ODataResponse<span class="token punctuation">></span></span><span class="token punctuation">(</span>content<span class="token punctuation">,</span> settings<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> values <span class="token operator">=</span> odataResp<span class="token operator">?</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Success</span><span class="token punctuation">(</span>values<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">FirstOrDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CrmHttpResponseException</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>Content<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span>
<span class="token string">"Failed to get D365 entity (Type= {@EntityType}). FetchXml: {@FetchXml}. Error: {@Exception}"</span><span class="token punctuation">,</span>
<span class="token function">nameof</span><span class="token punctuation">(</span>T<span class="token punctuation">)</span><span class="token punctuation">,</span> fetchXml<span class="token punctuation">,</span> ex
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Failure</span><span class="token punctuation">(</span>ex<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">finally</span>
<span class="token punctuation">{</span>
response<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator"><</span>Result<span class="token operator"><</span>IEnumerable<span class="token operator">></span><span class="token operator">></span> <span class="token function">GetMultiple_WithFetchXmlAsync</span><span class="token punctuation">(</span>
<span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> <span class="token keyword">string</span> fetchXml<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity
<span class="token punctuation">{</span>
<span class="token keyword">string</span> url <span class="token operator">=</span> $<span class="token string">"{_httpClient.BaseAddress.AbsoluteUri}/{entityPluralName}?fetchXml={fetchXml}"</span><span class="token punctuation">;</span>
HttpResponseMessage response <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> settings <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonSerializerSettings</span>
<span class="token punctuation">{</span>
Converters <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonConverter</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token class-name">JsonDynamicNameConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
Formatting <span class="token operator">=</span> Formatting<span class="token punctuation">.</span>None<span class="token punctuation">,</span>
DefaultValueHandling <span class="token operator">=</span> DefaultValueHandling<span class="token punctuation">.</span>Ignore
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">try</span>
<span class="token punctuation">{</span>
response <span class="token operator">=</span> <span class="token keyword">await</span> _httpClient<span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">.</span>IsSuccessStatusCode<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> content <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span>Content<span class="token punctuation">.</span><span class="token function">ReadAsStringAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ODataResponse odataResp <span class="token operator">=</span> JsonConvert<span class="token punctuation">.</span><span class="token generic-method function">DeserializeObject<span class="token punctuation"><</span>ODataResponse<span class="token punctuation">></span></span><span class="token punctuation">(</span>content<span class="token punctuation">,</span> settings<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> values <span class="token operator">=</span> odataResp<span class="token operator">?</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token generic-method function">Success<span class="token punctuation"><</span>IEnumerable<span class="token punctuation">></span></span><span class="token punctuation">(</span>values<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CrmHttpResponseException</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>Content<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span>
<span class="token string">"Failed to get multiple D365 entities (Type= {@EntityType}). FetchXml: {@FetchXml}. Error: {@Exception}"</span><span class="token punctuation">,</span>
<span class="token function">nameof</span><span class="token punctuation">(</span>T<span class="token punctuation">)</span><span class="token punctuation">,</span> fetchXml<span class="token punctuation">,</span> ex
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token generic-method function">Failure<span class="token punctuation"><</span>IEnumerable<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">finally</span>
<span class="token punctuation">{</span>
response<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">CreateAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> T item<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity
<span class="token punctuation">{</span>
<span class="token keyword">string</span> url <span class="token operator">=</span> $<span class="token string">"{_httpClient.BaseAddress.AbsoluteUri}/{entityPluralName}"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> settings <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonSerializerSettings</span>
<span class="token punctuation">{</span>
Converters <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonConverter</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token class-name">JsonDynamicNameConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
Formatting <span class="token operator">=</span> Formatting<span class="token punctuation">.</span>None<span class="token punctuation">,</span>
DefaultValueHandling <span class="token operator">=</span> DefaultValueHandling<span class="token punctuation">.</span>Ignore
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> json <span class="token operator">=</span> JsonConvert<span class="token punctuation">.</span><span class="token function">SerializeObject</span><span class="token punctuation">(</span>item<span class="token punctuation">,</span> settings<span class="token punctuation">)</span><span class="token punctuation">;</span>
HttpRequestMessage request <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HttpRequestMessage</span><span class="token punctuation">(</span>HttpMethod<span class="token punctuation">.</span>Post<span class="token punctuation">,</span> url<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Content <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringContent</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> Encoding<span class="token punctuation">.</span>UTF8<span class="token punctuation">,</span> <span class="token string">"application/json"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
HttpResponseMessage response <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token keyword">try</span>
<span class="token punctuation">{</span>
response <span class="token operator">=</span> <span class="token keyword">await</span> _httpClient<span class="token punctuation">.</span><span class="token function">SendAsync</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">.</span>IsSuccessStatusCode<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> oDataEntityId <span class="token operator">=</span> response<span class="token punctuation">.</span>Headers<span class="token punctuation">.</span><span class="token function">GetValues</span><span class="token punctuation">(</span><span class="token string">"OData-EntityId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">FirstOrDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>oDataEntityId<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">string</span> pattern <span class="token operator">=</span> <span class="token string">@"([a-z0-9]{8}[-][a-z0-9]{4}[-][a-z0-9]{4}[-][a-z0-9]{4}[-][a-z0-9]{12})"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> result <span class="token operator">=</span> Regex<span class="token punctuation">.</span><span class="token function">Match</span><span class="token punctuation">(</span>oDataEntityId<span class="token punctuation">,</span> pattern<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span>Success<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Information</span><span class="token punctuation">(</span>
<span class="token string">"D365 Entity (Type= @Entity) successfully created. EntityId: @EntityId"</span><span class="token punctuation">,</span>
<span class="token function">nameof</span><span class="token punctuation">(</span>T<span class="token punctuation">)</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span>Value
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Success</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Guid</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"Could not parse created record Id."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CrmHttpResponseException</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>Content<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span>
<span class="token string">"Failed to create D365 entity (Type= {@EntityType}). Entity: {@Entity}. Error: {@Exception}"</span><span class="token punctuation">,</span>
<span class="token function">nameof</span><span class="token punctuation">(</span>T<span class="token punctuation">)</span><span class="token punctuation">,</span> item<span class="token punctuation">,</span> ex
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Failure</span><span class="token punctuation">(</span>ex<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">finally</span>
<span class="token punctuation">{</span>
request<span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
response<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">UpdateAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> T updatedItem<span class="token punctuation">,</span> Guid entityId<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity
<span class="token punctuation">{</span>
<span class="token keyword">string</span> url <span class="token operator">=</span> $<span class="token string">"{_httpClient.BaseAddress.AbsoluteUri}/{entityPluralName}({entityId})"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> settings <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonSerializerSettings</span>
<span class="token punctuation">{</span>
Converters <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JsonConverter</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token class-name">JsonDynamicNameConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
Formatting <span class="token operator">=</span> Formatting<span class="token punctuation">.</span>None<span class="token punctuation">,</span>
DefaultValueHandling <span class="token operator">=</span> DefaultValueHandling<span class="token punctuation">.</span>Ignore
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> json <span class="token operator">=</span> JsonConvert<span class="token punctuation">.</span><span class="token function">SerializeObject</span><span class="token punctuation">(</span>updatedItem<span class="token punctuation">,</span> settings<span class="token punctuation">)</span><span class="token punctuation">;</span>
HttpRequestMessage request <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HttpRequestMessage</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">HttpMethod</span><span class="token punctuation">(</span><span class="token string">"Patch"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> url<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Content <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringContent</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> Encoding<span class="token punctuation">.</span>UTF8<span class="token punctuation">,</span> <span class="token string">"application/json"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
HttpResponseMessage response <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token keyword">try</span>
<span class="token punctuation">{</span>
response <span class="token operator">=</span> <span class="token keyword">await</span> _httpClient<span class="token punctuation">.</span><span class="token function">SendAsync</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">.</span>IsSuccessStatusCode<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Information</span><span class="token punctuation">(</span>
<span class="token string">"D365 Entity (Type= {@Entity}) successfully updated. EntityId: {@EntityId}"</span><span class="token punctuation">,</span>
<span class="token function">nameof</span><span class="token punctuation">(</span>T<span class="token punctuation">)</span><span class="token punctuation">,</span> entityId
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Success</span><span class="token punctuation">(</span>entityId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CrmHttpResponseException</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>Content<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span>
<span class="token string">"Failed to update D365 entity (Type= {@EntityType}). Entity: {@Entity}. Error: {@Exception}"</span><span class="token punctuation">,</span>
<span class="token function">nameof</span><span class="token punctuation">(</span>T<span class="token punctuation">)</span><span class="token punctuation">,</span> updatedItem<span class="token punctuation">,</span> ex
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Failure</span><span class="token punctuation">(</span>ex<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">finally</span>
<span class="token punctuation">{</span>
request<span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
response<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">DeleteAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> entityPluralName<span class="token punctuation">,</span> Guid entityId<span class="token punctuation">)</span> <span class="token keyword">where</span> T <span class="token punctuation">:</span> BaseEntity
<span class="token punctuation">{</span>
<span class="token keyword">string</span> url <span class="token operator">=</span> $<span class="token string">"{_httpClient.BaseAddress.AbsoluteUri}/{entityPluralName}({entityId})"</span><span class="token punctuation">;</span>
HttpResponseMessage response <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token keyword">try</span>
<span class="token punctuation">{</span>
response <span class="token operator">=</span> <span class="token keyword">await</span> _httpClient<span class="token punctuation">.</span><span class="token function">DeleteAsync</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">.</span>IsSuccessStatusCode<span class="token punctuation">)</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Success</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CrmHttpResponseException</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>Content<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span>
<span class="token string">"Failed to delete D365 entity (Type= {@EntityType}). EntityId: {@EntityId}. Error: {@Exception}"</span><span class="token punctuation">,</span>
<span class="token function">nameof</span><span class="token punctuation">(</span>T<span class="token punctuation">)</span><span class="token punctuation">,</span> entityId<span class="token punctuation">,</span> ex
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">Failure</span><span class="token punctuation">(</span>ex<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">finally</span>
<span class="token punctuation">{</span>
response<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
The Application Layer (Worker Service)
This layer contains a single console app – worker service. It is where we wire up all dependencies needed for our Quartz jobs. The building blocks are as follows:
- Wiring up dependencies
- Building the job (s)
- Bootstrapping the job (s)
Wiring up dependencies
I am using the built-in .Net Core DI container but you can use any other DI containers (eg. Autofac, Simple Injector). The concept is still the same.
For settings and configurations, I use IOptions<TOptions> pattern in .Net Core. We collect all settings and configurations from the appsettings<span class="token punctuation">.</span>Development<span class="token punctuation">.</span>json
file.
<span class="token comment">// For configurations</span>
services<span class="token punctuation">.</span><span class="token function">AddOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token generic-method function">Configure<span class="token punctuation"><</span>WorkerOptions<span class="token punctuation">></span></span><span class="token punctuation">(</span>
workerOptions <span class="token operator">=</span><span class="token operator">></span> hostContext<span class="token punctuation">.</span>Configuration<span class="token punctuation">.</span><span class="token function">Bind</span><span class="token punctuation">(</span><span class="token string">"WorkerOptions"</span><span class="token punctuation">,</span> workerOptions<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
To interact with CRM Web API endpoint, we are using an instance of HttpClient. A IHttpClientFactory is used to create an instance of this class and have it properly injected to the repository. This factory takes care of disposing the client’s instance.
I also created a Http Message Handler
to acquire an access token from CRM every time we send a request from the worker to CRM through HttpClient
. This handler is configured to be the primary handler for all out going requests.
<span class="token comment">// For crm odata endpoint</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddHttpClient<span class="token punctuation"><</span>ICrmRepository<span class="token punctuation">,</span> CrmRepository<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>serviceProvider<span class="token punctuation">,</span> http<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> options <span class="token operator">=</span> serviceProvider<span class="token punctuation">.</span><span class="token generic-method function">GetService<span class="token punctuation"><</span>IOptions<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span>
http<span class="token punctuation">.</span>BaseAddress <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>AdOption<span class="token punctuation">.</span>OrganizationUrl <span class="token operator">+</span> <span class="token string">"/"</span> <span class="token operator">+</span> options<span class="token punctuation">.</span>AdOption<span class="token punctuation">.</span>CrmApi<span class="token punctuation">)</span><span class="token punctuation">;</span>
http<span class="token punctuation">.</span>DefaultRequestHeaders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token string">"OData-MaxVersion"</span><span class="token punctuation">,</span> <span class="token string">"4.0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
http<span class="token punctuation">.</span>DefaultRequestHeaders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token string">"OData-Version"</span><span class="token punctuation">,</span> <span class="token string">"4.0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
http<span class="token punctuation">.</span>DefaultRequestHeaders<span class="token punctuation">.</span>Accept<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>
<span class="token keyword">new</span> <span class="token class-name">MediaTypeWithQualityHeaderValue</span><span class="token punctuation">(</span><span class="token string">"application/json"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">ConfigurePrimaryHttpMessageHandler</span><span class="token punctuation">(</span><span class="token punctuation">(</span>serviceProvider<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">new</span> <span class="token class-name">CrmAuth</span><span class="token punctuation">(</span>
serviceProvider<span class="token punctuation">.</span><span class="token generic-method function">GetService<span class="token punctuation"><</span>IOptions<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Value<span class="token punctuation">.</span>AdOption<span class="token punctuation">)</span><span class="token punctuation">.</span>ClientHandler<span class="token punctuation">)</span><span class="token punctuation">;</span>
CrmAuth<span class="token punctuation">.</span>cs
:
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>IdentityModel<span class="token punctuation">.</span>Clients<span class="token punctuation">.</span>ActiveDirectory<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>Http<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>Http<span class="token punctuation">.</span>Headers<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks<span class="token punctuation">;</span>
<span class="token keyword">namespace</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker<span class="token punctuation">.</span>Handlers
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CrmAuth</span>
<span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> AdOption _azureAdOptions<span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> AuthenticationContext _context<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token function">CrmAuth</span><span class="token punctuation">(</span>AdOption azureAdOptions<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
_azureAdOptions <span class="token operator">=</span> azureAdOptions<span class="token punctuation">;</span>
<span class="token comment">// Target deployment: CRM online => use OAuthMessageHandler</span>
ClientHandler <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OAuthMessageHandler</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">HttpClientHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
_context <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AuthenticationContext</span><span class="token punctuation">(</span>_azureAdOptions<span class="token punctuation">.</span>Instance <span class="token operator">+</span> _azureAdOptions<span class="token punctuation">.</span>TenantId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> HttpMessageHandler ClientHandler <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token preprocessor property">#<span class="token directive keyword">region</span> Methods</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">AcquireTokenAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">await</span> _context<span class="token punctuation">.</span><span class="token function">AcquireTokenAsync</span><span class="token punctuation">(</span>
_azureAdOptions<span class="token punctuation">.</span>OrganizationUrl<span class="token punctuation">,</span>
<span class="token keyword">new</span> <span class="token class-name">ClientCredential</span><span class="token punctuation">(</span>_azureAdOptions<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> _azureAdOptions<span class="token punctuation">.</span>ClientSecret<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token preprocessor property">#<span class="token directive keyword">endregion</span></span>
<span class="token preprocessor property">#<span class="token directive keyword">region</span> OAuthMessageHandler for CRM Online (For On-Prem, use HttpClientHandler)</span>
<span class="token keyword">class</span> <span class="token class-name">OAuthMessageHandler</span> <span class="token punctuation">:</span> DelegatingHandler
<span class="token punctuation">{</span>
<span class="token keyword">readonly</span> CrmAuth _auth <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token function">OAuthMessageHandler</span><span class="token punctuation">(</span>CrmAuth auth<span class="token punctuation">,</span> HttpMessageHandler innerHandler<span class="token punctuation">)</span>
<span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>innerHandler<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
_auth <span class="token operator">=</span> auth<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">protected</span> <span class="token keyword">override</span> Task <span class="token function">SendAsync</span><span class="token punctuation">(</span>
HttpRequestMessage request<span class="token punctuation">,</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token comment">// It is a best practice to refresh the access token before every message request is sent. Doing so</span>
<span class="token comment">// avoids having to check the expiration date/time of the token. This operation is quick.</span>
<span class="token keyword">var</span> token <span class="token operator">=</span> _auth<span class="token punctuation">.</span><span class="token function">AcquireTokenAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Result<span class="token punctuation">.</span>AccessToken<span class="token punctuation">;</span>
request<span class="token punctuation">.</span>Headers<span class="token punctuation">.</span>Authorization <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AuthenticationHeaderValue</span><span class="token punctuation">(</span><span class="token string">"Bearer"</span><span class="token punctuation">,</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">SendAsync</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token preprocessor property">#<span class="token directive keyword">endregion</span></span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
For fetching stock items from the local database, an implementation of IStockItemRepository
is registered.
<span class="token comment">// For local database</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation"><</span>IStockItemRepository<span class="token punctuation">></span></span><span class="token punctuation">(</span>serviceProvider <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">new</span> <span class="token class-name">StockItemRepository</span><span class="token punctuation">(</span>
serviceProvider<span class="token punctuation">.</span><span class="token generic-method function">GetService<span class="token punctuation"><</span>IOptions<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Value<span class="token punctuation">.</span>ConnectionString<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
To make dependencies available to our Quartz job, we need to do a bit of extra work.
We implement the IJobFactory
interface – SingletonJobFactory
is class that holds a few methods to create and dispose an instance of our job.
SingletonJobFactory<span class="token punctuation">.</span>cs<span class="token punctuation">:</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>DependencyInjection<span class="token punctuation">;</span>
<span class="token keyword">using</span> Quartz<span class="token punctuation">;</span>
<span class="token keyword">using</span> Quartz<span class="token punctuation">.</span>Spi<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">;</span>
<span class="token keyword">namespace</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker<span class="token punctuation">.</span>Quartz
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SingletonJobFactory</span> <span class="token punctuation">:</span> IJobFactory
<span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> IServiceProvider _serviceProvider<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token function">SingletonJobFactory</span><span class="token punctuation">(</span>IServiceProvider serviceProvider<span class="token punctuation">)</span>
<span class="token operator">=</span><span class="token operator">></span> _serviceProvider <span class="token operator">=</span> serviceProvider<span class="token punctuation">;</span>
<span class="token keyword">public</span> IJob <span class="token function">NewJob</span><span class="token punctuation">(</span>TriggerFiredBundle bundle<span class="token punctuation">,</span> IScheduler scheduler<span class="token punctuation">)</span>
<span class="token operator">=</span><span class="token operator">></span> _serviceProvider<span class="token punctuation">.</span><span class="token function">GetRequiredService</span><span class="token punctuation">(</span>bundle<span class="token punctuation">.</span>JobDetail<span class="token punctuation">.</span>JobType<span class="token punctuation">)</span> <span class="token keyword">as</span> IJob<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">ReturnJob</span><span class="token punctuation">(</span>IJob job<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token punctuation">(</span>job <span class="token keyword">as</span> IDisposable<span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
We also use the default implementation of ISchedulerFactory
– StdSchedulerFactory
to schedule our job with a cron expression.
<span class="token comment">// Add Quartz services</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation"><</span>IJobFactory<span class="token punctuation">,</span> SingletonJobFactory<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation"><</span>ISchedulerFactory<span class="token punctuation">,</span> StdSchedulerFactory<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
For scheduling the job and the job itself, we’ll just inject an instance of Job Schedule and wrap it with the ItemSyncJob. If you want to add another job, just do the same way like bellow but be aware that this maybe a bit verbose. I’ll talk more on this as we’re moving along.
<span class="token comment">// for job and job schedule</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation"><</span>ItemSyncJob<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span>serviceProvider <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">new</span> <span class="token class-name">JobSchedule</span><span class="token punctuation">(</span>
jobType<span class="token punctuation">:</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span>ItemSyncJob<span class="token punctuation">)</span><span class="token punctuation">,</span>
cronExpression<span class="token punctuation">:</span> serviceProvider<span class="token punctuation">.</span>GetService<span class="token operator"><</span>IOptions<span class="token operator"><</span>WorkerOptions<span class="token operator">></span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Value<span class="token punctuation">.</span>CronExpression<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddHostedService<span class="token punctuation"><</span>QuartzHostedService<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ItemSyncJob
is an implementation IJob<span class="token punctuation">,</span>
we’ll come back to this class in a moment.
The complete Program<span class="token punctuation">.</span>cs
:
<span class="token keyword">using</span> System<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Globalization<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>Http<span class="token punctuation">.</span>Headers<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Data<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker<span class="token punctuation">.</span>Handlers<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker<span class="token punctuation">.</span>Quartz<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Configuration<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>DependencyInjection<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Hosting<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Options<span class="token punctuation">;</span>
<span class="token keyword">using</span> Quartz<span class="token punctuation">;</span>
<span class="token keyword">using</span> Quartz<span class="token punctuation">.</span>Impl<span class="token punctuation">;</span>
<span class="token keyword">using</span> Quartz<span class="token punctuation">.</span>Spi<span class="token punctuation">;</span>
<span class="token keyword">using</span> Serilog<span class="token punctuation">;</span>
<span class="token keyword">using</span> Serilog<span class="token punctuation">.</span>Events<span class="token punctuation">;</span>
<span class="token keyword">namespace</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Program</span>
<span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span> ENVIRONMENT <span class="token operator">=</span> Environment<span class="token punctuation">.</span><span class="token function">GetEnvironmentVariable</span><span class="token punctuation">(</span><span class="token string">"DOTNET_ENVIRONMENT"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">Main</span><span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span>Logger <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LoggerConfiguration</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span>MinimumLevel<span class="token punctuation">.</span><span class="token function">Debug</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span>MinimumLevel<span class="token punctuation">.</span><span class="token function">Override</span><span class="token punctuation">(</span><span class="token string">"Microsoft"</span><span class="token punctuation">,</span> LogEventLevel<span class="token punctuation">.</span>Information<span class="token punctuation">)</span>
<span class="token punctuation">.</span>Enrich<span class="token punctuation">.</span><span class="token function">WithProperty</span><span class="token punctuation">(</span><span class="token string">"Environment"</span><span class="token punctuation">,</span> ENVIRONMENT<span class="token punctuation">)</span>
<span class="token punctuation">.</span>Enrich<span class="token punctuation">.</span><span class="token function">WithProperty</span><span class="token punctuation">(</span><span class="token string">"Application"</span><span class="token punctuation">,</span> $<span class="token string">"DIT Integration Worker Service - {ENVIRONMENT}"</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span>WriteTo<span class="token punctuation">.</span><span class="token function">Console</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span>WriteTo<span class="token punctuation">.</span><span class="token function">Seq</span><span class="token punctuation">(</span><span class="token string">"http://your-seq-server-ip"</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">CreateLogger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">CreateHostBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> IHostBuilder <span class="token function">CreateHostBuilder</span><span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
Host<span class="token punctuation">.</span><span class="token function">CreateDefaultBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">UseEnvironment</span><span class="token punctuation">(</span>ENVIRONMENT<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">UseSerilog</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">ConfigureAppConfiguration</span><span class="token punctuation">(</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> configBuilder<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> env <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CultureInfo</span><span class="token punctuation">(</span><span class="token string">"en-US"</span><span class="token punctuation">,</span> <span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">.</span>TextInfo<span class="token punctuation">.</span><span class="token function">ToTitleCase</span><span class="token punctuation">(</span>
host<span class="token punctuation">.</span>HostingEnvironment<span class="token punctuation">.</span>EnvironmentName<span class="token punctuation">)</span><span class="token punctuation">;</span>
configBuilder<span class="token punctuation">.</span><span class="token function">SetBasePath</span><span class="token punctuation">(</span>host<span class="token punctuation">.</span>HostingEnvironment<span class="token punctuation">.</span>ContentRootPath<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">AddJsonFile</span><span class="token punctuation">(</span><span class="token string">"appsettings.json"</span><span class="token punctuation">,</span> optional<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> reloadOnChange<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">AddJsonFile</span><span class="token punctuation">(</span>$<span class="token string">"appsettings.{env}.json"</span><span class="token punctuation">,</span>
optional<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span> reloadOnChange<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">ConfigureServices</span><span class="token punctuation">(</span><span class="token punctuation">(</span>hostContext<span class="token punctuation">,</span> services<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
<span class="token punctuation">{</span>
<span class="token comment">// For configurations</span>
services<span class="token punctuation">.</span><span class="token function">AddOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Configure</span><span class="token punctuation">(</span>
workerOptions <span class="token operator">=</span><span class="token operator">></span> hostContext<span class="token punctuation">.</span>Configuration<span class="token punctuation">.</span><span class="token function">Bind</span><span class="token punctuation">(</span><span class="token string">"WorkerOptions"</span><span class="token punctuation">,</span> workerOptions<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// For crm odata endpoint</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddHttpClient<span class="token punctuation"><</span>ICrmRepository<span class="token punctuation">,</span> CrmRepository<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>serviceProvider<span class="token punctuation">,</span> http<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> options <span class="token operator">=</span> serviceProvider<span class="token punctuation">.</span><span class="token generic-method function">GetService<span class="token punctuation"><</span>IOptions<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span>
http<span class="token punctuation">.</span>BaseAddress <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>AdOption<span class="token punctuation">.</span>OrganizationUrl <span class="token operator">+</span> <span class="token string">"/"</span> <span class="token operator">+</span> options<span class="token punctuation">.</span>AdOption<span class="token punctuation">.</span>CrmApi<span class="token punctuation">)</span><span class="token punctuation">;</span>
http<span class="token punctuation">.</span>DefaultRequestHeaders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token string">"OData-MaxVersion"</span><span class="token punctuation">,</span> <span class="token string">"4.0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
http<span class="token punctuation">.</span>DefaultRequestHeaders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token string">"OData-Version"</span><span class="token punctuation">,</span> <span class="token string">"4.0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
http<span class="token punctuation">.</span>DefaultRequestHeaders<span class="token punctuation">.</span>Accept<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>
<span class="token keyword">new</span> <span class="token class-name">MediaTypeWithQualityHeaderValue</span><span class="token punctuation">(</span><span class="token string">"application/json"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">ConfigurePrimaryHttpMessageHandler</span><span class="token punctuation">(</span><span class="token punctuation">(</span>serviceProvider<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">new</span> <span class="token class-name">CrmAuth</span><span class="token punctuation">(</span>
serviceProvider<span class="token punctuation">.</span><span class="token generic-method function">GetService<span class="token punctuation"><</span>IOptions<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Value<span class="token punctuation">.</span>AdOption<span class="token punctuation">)</span><span class="token punctuation">.</span>ClientHandler<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// For local database</span>
services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span>serviceProvider <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">new</span> <span class="token class-name">StockItemRepository</span><span class="token punctuation">(</span>
serviceProvider<span class="token punctuation">.</span><span class="token generic-method function">GetService<span class="token punctuation"><</span>IOptions<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Value<span class="token punctuation">.</span>ConnectionString<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Add Quartz services</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation"><</span>IJobFactory<span class="token punctuation">,</span> SingletonJobFactory<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation"><</span>ISchedulerFactory<span class="token punctuation">,</span> StdSchedulerFactory<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span>serviceProvider <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">new</span> <span class="token class-name">JobSchedule</span><span class="token punctuation">(</span>
jobType<span class="token punctuation">:</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span>ItemSyncJob<span class="token punctuation">)</span><span class="token punctuation">,</span>
cronExpression<span class="token punctuation">:</span> serviceProvider<span class="token punctuation">.</span><span class="token generic-method function">GetService<span class="token punctuation"><</span>IOptions<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Value<span class="token punctuation">.</span>CronExpression<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
services<span class="token punctuation">.</span><span class="token function">AddHostedService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
Building our job (s)
The actual sync occurs in ItemSyncJob<span class="token punctuation">.</span>cs<span class="token punctuation">.</span>
As you can tell we are injecting the dependencies into it. The only method you need to pay attention to is the Task <span class="token function">Execute</span><span class="token punctuation">(</span>IJobExecutionContext context<span class="token punctuation">)</span><span class="token punctuation">.</span>
Others are just private methods, you can add your own logic here.
<span class="token keyword">using</span> CSharpFunctionalExtensions<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Data<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Domain<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>Crm<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Domain<span class="token punctuation">.</span>OData<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker<span class="token punctuation">.</span>Helpers<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Options<span class="token punctuation">;</span>
<span class="token keyword">using</span> Quartz<span class="token punctuation">;</span>
<span class="token keyword">using</span> Serilog<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks<span class="token punctuation">;</span>
<span class="token keyword">namespace</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker<span class="token punctuation">.</span>Quartz
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ItemSyncJob</span>
<span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> IStockItemRepository _stockRepository<span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> ICrmRepository _crmRepository<span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> WorkerOptions _options<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token function">ItemSyncJob</span><span class="token punctuation">(</span>IStockItemRepository stockRepository<span class="token punctuation">,</span> ICrmRepository crmRepository<span class="token punctuation">,</span>
IOptions options<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
_crmRepository <span class="token operator">=</span> crmRepository <span class="token operator">?</span><span class="token operator">?</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token function">nameof</span><span class="token punctuation">(</span>crmRepository<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
_stockRepository <span class="token operator">=</span> stockRepository <span class="token operator">?</span><span class="token operator">?</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token function">nameof</span><span class="token punctuation">(</span>stockRepository<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
_options <span class="token operator">=</span> options<span class="token operator">?</span><span class="token punctuation">.</span>Value <span class="token operator">?</span><span class="token operator">?</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token function">nameof</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> Task <span class="token function">Execute</span><span class="token punctuation">(</span>IJobExecutionContext context<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> Task<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token function">SyncStockItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">SyncStockItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Information</span><span class="token punctuation">(</span><span class="token string">"Items sync started on {Started}."</span><span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> itemsResult <span class="token operator">=</span> _stockRepository<span class="token punctuation">.</span><span class="token function">GetAllItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>itemsResult<span class="token punctuation">.</span>IsSuccess<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">foreach</span> <span class="token punctuation">(</span>StockItem item <span class="token keyword">in</span> itemsResult<span class="token punctuation">.</span>Value<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Log<span class="token punctuation">.</span><span class="token function">Debug</span><span class="token punctuation">(</span><span class="token string">"Stock Item No is {StockItemNo}"</span><span class="token punctuation">,</span> item<span class="token punctuation">.</span>StockCode<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> _crmRepository<span class="token punctuation">.</span><span class="token function">GetWithFetchXmlAsync</span><span class="token punctuation">(</span>
PluralNameConstants<span class="token punctuation">.</span>Products<span class="token punctuation">,</span> FetchXmlHelper<span class="token punctuation">.</span><span class="token function">GetProductBy</span><span class="token punctuation">(</span>item<span class="token punctuation">.</span>StockCode<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Tap</span><span class="token punctuation">(</span>product <span class="token operator">=</span><span class="token operator">></span> <span class="token function">UpsertProduct</span><span class="token punctuation">(</span>product<span class="token punctuation">,</span> item<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Tap</span><span class="token punctuation">(</span>productId <span class="token operator">=</span><span class="token operator">></span> <span class="token function">UpsertPriceLevel</span><span class="token punctuation">(</span>productId<span class="token punctuation">,</span> item<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Tap</span><span class="token punctuation">(</span>productId <span class="token operator">=</span><span class="token operator">></span> <span class="token function">SetActiveProductStatus</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">private</span> <span class="token keyword">async</span> Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">UpsertProduct</span><span class="token punctuation">(</span>Product product<span class="token punctuation">,</span> StockItem stockItem<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Result upsertResult <span class="token operator">=</span> Result<span class="token punctuation">.</span><span class="token function">Ok</span><span class="token punctuation">(</span>product<span class="token punctuation">.</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>product <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
product <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Product</span>
<span class="token punctuation">{</span>
Id <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
ProductNumber <span class="token operator">=</span> stockItem<span class="token punctuation">.</span>StockCode<span class="token punctuation">,</span>
Name <span class="token operator">=</span> stockItem<span class="token punctuation">.</span>StockCode<span class="token punctuation">,</span>
CurrentCost <span class="token operator">=</span> stockItem<span class="token punctuation">.</span>RetailPriceInc<span class="token punctuation">,</span>
DefaultUomScheduleId <span class="token operator">=</span> _options<span class="token punctuation">.</span>SalesOptions<span class="token punctuation">.</span>UoMScheduleId<span class="token punctuation">,</span>
DefaultUomId <span class="token operator">=</span> _options<span class="token punctuation">.</span>SalesOptions<span class="token punctuation">.</span>UoMId<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
upsertResult <span class="token operator">=</span> <span class="token keyword">await</span> _crmRepository<span class="token punctuation">.</span><span class="token function">CreateAsync</span><span class="token punctuation">(</span>PluralNameConstants<span class="token punctuation">.</span>Products<span class="token punctuation">,</span> product<span class="token punctuation">)</span><span class="token punctuation">.</span>
<span class="token function">Tap</span><span class="token punctuation">(</span>id <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Debug</span><span class="token punctuation">(</span><span class="token string">"Product(ID: {@ProductID}) is created on {CreatedOn}."</span><span class="token punctuation">,</span> product<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">OnFailure</span><span class="token punctuation">(</span>error <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">"Failed to push Stock Item(StockCode: {@StockCode}) to D365. Error: {@Error}"</span><span class="token punctuation">,</span> product<span class="token punctuation">.</span>ProductNumber<span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">else</span>
<span class="token punctuation">{</span>
upsertResult <span class="token operator">=</span> <span class="token keyword">await</span> _crmRepository<span class="token punctuation">.</span><span class="token function">UpdateAsync</span><span class="token punctuation">(</span>
PluralNameConstants<span class="token punctuation">.</span>Products<span class="token punctuation">,</span> product<span class="token punctuation">,</span> product<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Tap</span><span class="token punctuation">(</span>id <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Debug</span><span class="token punctuation">(</span><span class="token string">"Product(ID: {@ProductID}) is updated at {@ModifiedOn}."</span><span class="token punctuation">,</span> product<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">OnFailure</span><span class="token punctuation">(</span>error <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">"Failed to update Product(ID: {@ProductID}). Error {Error}"</span><span class="token punctuation">,</span> product<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> upsertResult<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">private</span> <span class="token keyword">async</span> Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">UpsertPriceLevel</span><span class="token punctuation">(</span>Guid productId<span class="token punctuation">,</span> StockItem stockItem<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">await</span> _crmRepository<span class="token punctuation">.</span><span class="token function">GetWithFetchXmlAsync</span><span class="token punctuation">(</span>
PluralNameConstants<span class="token punctuation">.</span>ProductPriceLevels<span class="token punctuation">,</span> FetchXmlHelper<span class="token punctuation">.</span><span class="token function">FindProductPriceLevelBy</span><span class="token punctuation">(</span>
productId<span class="token punctuation">,</span> _options<span class="token punctuation">.</span>SalesOptions<span class="token punctuation">.</span>DefaultPriceList<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Bind</span><span class="token punctuation">(</span>priceLevel <span class="token operator">=</span><span class="token operator">></span>
<span class="token punctuation">{</span>
Task<span class="token operator"><</span>Result<span class="token operator">></span> upsertResult<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>priceLevel <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
priceLevel <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ProductPriceLevel</span>
<span class="token punctuation">{</span>
Id <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
DefaultPriceList <span class="token operator">=</span> _options<span class="token punctuation">.</span>SalesOptions<span class="token punctuation">.</span>DefaultPriceList<span class="token punctuation">,</span>
ProductId <span class="token operator">=</span> productId<span class="token punctuation">,</span>
DefaultUomId <span class="token operator">=</span> _options<span class="token punctuation">.</span>SalesOptions<span class="token punctuation">.</span>UoMId<span class="token punctuation">,</span>
Amount <span class="token operator">=</span> stockItem<span class="token punctuation">.</span>ResellerPriceInc
<span class="token punctuation">}</span><span class="token punctuation">;</span>
upsertResult <span class="token operator">=</span> _crmRepository<span class="token punctuation">.</span><span class="token function">CreateAsync</span><span class="token punctuation">(</span>PluralNameConstants<span class="token punctuation">.</span>ProductPriceLevels<span class="token punctuation">,</span> priceLevel<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Tap</span><span class="token punctuation">(</span>id <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Debug</span><span class="token punctuation">(</span><span class="token string">"Product({@ProductId}) Price was created on {@CreatedOn}."</span><span class="token punctuation">,</span> productId<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">else</span>
<span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>priceLevel<span class="token punctuation">.</span>Amount <span class="token operator">==</span> stockItem<span class="token punctuation">.</span>ResellerPriceInc<span class="token punctuation">)</span>
<span class="token keyword">return</span> Task<span class="token punctuation">.</span><span class="token function">FromResult</span><span class="token punctuation">(</span>
Result<span class="token punctuation">.</span><span class="token function">Ok</span><span class="token punctuation">(</span>priceLevel<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Tap</span><span class="token punctuation">(</span>id <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Information</span><span class="token punctuation">(</span><span class="token string">"Product Price for Product({@ProductPriceId}) was unchanged."</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
priceLevel<span class="token punctuation">.</span>Amount <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">Round</span><span class="token punctuation">(</span>stockItem<span class="token punctuation">.</span>ResellerPriceInc<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
upsertResult <span class="token operator">=</span> _crmRepository<span class="token punctuation">.</span><span class="token function">UpdateAsync</span><span class="token punctuation">(</span>PluralNameConstants<span class="token punctuation">.</span>ProductPriceLevels<span class="token punctuation">,</span> priceLevel<span class="token punctuation">,</span> priceLevel<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Tap</span><span class="token punctuation">(</span>id <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Debug</span><span class="token punctuation">(</span><span class="token string">"Product({@ProductId}) Price was updated on {@ModifiedOn}."</span><span class="token punctuation">,</span> productId<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> upsertResult<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">private</span> <span class="token keyword">async</span> Task<span class="token operator"><</span>Result<span class="token operator">></span> <span class="token function">SetActiveProductStatus</span><span class="token punctuation">(</span>Guid productId<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">await</span> _crmRepository<span class="token punctuation">.</span><span class="token function">UpdateAsync</span><span class="token punctuation">(</span>
PluralNameConstants<span class="token punctuation">.</span>Products<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Product</span>
<span class="token punctuation">{</span>
Id <span class="token operator">=</span> productId<span class="token punctuation">,</span>
StateCode <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span>
StatusCode <span class="token operator">=</span> <span class="token number">1</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> productId<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Tap</span><span class="token punctuation">(</span>id <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Debug</span><span class="token punctuation">(</span><span class="token string">"Product({@ProductId}) State was updated to Active on {@ModifiedOn}."</span><span class="token punctuation">,</span> id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">OnFailure</span><span class="token punctuation">(</span>error <span class="token operator">=</span><span class="token operator">></span> Log<span class="token punctuation">.</span><span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">"Failed to update product to Active state. Error: {@Error}."</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
Bootstrapping the job (s)
For the sync job, I’m using the IHostedService
interface instead of the BackgroundService
that was generated with the template. See this link for an overview of BackgroundService, and this one for the IHostedService interface.
The interface has two members (specifically methods):
Task <span class="token function">StartAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
Task <span class="token function">StopAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
You’ll understand why I use this interface but not the BackgroundService
in just a bit.
QuartzHostedService<span class="token punctuation">.</span>cs
:
<span class="token keyword">using</span> System<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Collections<span class="token punctuation">.</span>Generic<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Linq<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Hosting<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Logging<span class="token punctuation">;</span>
<span class="token keyword">namespace</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">QuartzHostedService</span> <span class="token punctuation">:</span> IHostedService
<span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> ILogger _logger<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token function">QuartzHostedService</span><span class="token punctuation">(</span>ILogger logger<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
_logger <span class="token operator">=</span> logger<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> Task <span class="token function">StartAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> Task<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span>
<span class="token punctuation">{</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>cancellationToken<span class="token punctuation">.</span>IsCancellationRequested<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
_logger<span class="token punctuation">.</span><span class="token function">LogInformation</span><span class="token punctuation">(</span><span class="token string">"Worker running at: {time}"</span><span class="token punctuation">,</span> DateTimeOffset<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> Task <span class="token function">StopAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NotImplementedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
You’ve probably noticed the generated template came with an ILogger. This is the .NET Core’s built-in logger but we are not using it. Instead, I’m using Serilog, it’s my preferred logger but it’s up to you. They’re both structured event loggers.
We are going to have two private methods. One for creating our job and the other to create a trigger for it. This trigger is based on Cron Expression.
<span class="token keyword">private</span> <span class="token keyword">static</span> IJobDetail <span class="token function">CreateJob</span><span class="token punctuation">(</span>JobSchedule schedule<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> jobType <span class="token operator">=</span> schedule<span class="token punctuation">.</span>JobType<span class="token punctuation">;</span>
<span class="token keyword">return</span> JobBuilder
<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>jobType<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithIdentity</span><span class="token punctuation">(</span>jobType<span class="token punctuation">.</span>FullName<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithDescription</span><span class="token punctuation">(</span>jobType<span class="token punctuation">.</span>Name<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> ITrigger <span class="token function">CreateTrigger</span><span class="token punctuation">(</span>JobSchedule schedule<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> TriggerBuilder
<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithIdentity</span><span class="token punctuation">(</span>$<span class="token string">"{schedule.JobType.FullName}.trigger"</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithCronSchedule</span><span class="token punctuation">(</span>schedule<span class="token punctuation">.</span>CronExpression<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithDescription</span><span class="token punctuation">(</span>schedule<span class="token punctuation">.</span>CronExpression<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
JobSchedule
is a simple class to hold information about the job type and its Cron Expression. We collect these from the dependencies we registered earlier. You should put them in the appsettings or any configuration file if you have many jobs to run (eg. an array of job configurations). I only have a single job so that’ll do for me.
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">JobSchedule</span>
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token function">JobSchedule</span><span class="token punctuation">(</span>Type jobType<span class="token punctuation">,</span> <span class="token keyword">string</span> cronExpression<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
JobType <span class="token operator">=</span> jobType<span class="token punctuation">;</span>
CronExpression <span class="token operator">=</span> cronExpression<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> Type JobType <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">string</span> CronExpression <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
Creating and scheduling our job (s) is done inside the Task <span class="token function">StartAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
. This method is triggered when the application host is ready to start the service.
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">StartAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Scheduler <span class="token operator">=</span> <span class="token keyword">await</span> _schedulerFactory<span class="token punctuation">.</span><span class="token function">GetScheduler</span><span class="token punctuation">(</span>cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
Scheduler<span class="token punctuation">.</span>JobFactory <span class="token operator">=</span> _jobFactory<span class="token punctuation">;</span>
<span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token keyword">var</span> jobSchedule <span class="token keyword">in</span> _jobSchedules<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> job <span class="token operator">=</span> <span class="token function">CreateJob</span><span class="token punctuation">(</span>jobSchedule<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> trigger <span class="token operator">=</span> <span class="token function">CreateTrigger</span><span class="token punctuation">(</span>jobSchedule<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> Scheduler<span class="token punctuation">.</span><span class="token function">ScheduleJob</span><span class="token punctuation">(</span>job<span class="token punctuation">,</span> trigger<span class="token punctuation">,</span> cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">await</span> Scheduler<span class="token punctuation">.</span><span class="token function">Start</span><span class="token punctuation">(</span>cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
Task <span class="token function">StopAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
is triggered when the application host is performing a graceful shutdown. This is where we want to halt the Quartz.IScheduler’s firing of Quartz.ITriggers, and cleans up all resources.
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">StopAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">await</span> Scheduler<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Shutdown</span><span class="token punctuation">(</span>cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
The complete QuartzHostedService<span class="token punctuation">.</span>cs
:
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Collections<span class="token punctuation">.</span>Generic<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">;</span>
<span class="token keyword">using</span> System<span class="token punctuation">.</span>Threading<span class="token punctuation">.</span>Tasks<span class="token punctuation">;</span>
<span class="token keyword">using</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker<span class="token punctuation">.</span>Quartz<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Extensions<span class="token punctuation">.</span>Hosting<span class="token punctuation">;</span>
<span class="token keyword">using</span> Quartz<span class="token punctuation">;</span>
<span class="token keyword">using</span> Quartz<span class="token punctuation">.</span>Spi<span class="token punctuation">;</span>
<span class="token keyword">namespace</span> DIT<span class="token punctuation">.</span>IntegrationService<span class="token punctuation">.</span>Worker
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">QuartzHostedService</span> <span class="token punctuation">:</span> IHostedService
<span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> ISchedulerFactory _schedulerFactory<span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> IJobFactory _jobFactory<span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">readonly</span> IEnumerable _jobSchedules<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token function">QuartzHostedService</span><span class="token punctuation">(</span>
ISchedulerFactory schedulerFactory<span class="token punctuation">,</span>
IJobFactory jobFactory<span class="token punctuation">,</span>
IEnumerable jobSchedules<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
_schedulerFactory <span class="token operator">=</span> schedulerFactory<span class="token punctuation">;</span>
_jobSchedules <span class="token operator">=</span> jobSchedules<span class="token punctuation">;</span>
_jobFactory <span class="token operator">=</span> jobFactory<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> IScheduler Scheduler <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">StartAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
Scheduler <span class="token operator">=</span> <span class="token keyword">await</span> _schedulerFactory<span class="token punctuation">.</span><span class="token function">GetScheduler</span><span class="token punctuation">(</span>cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
Scheduler<span class="token punctuation">.</span>JobFactory <span class="token operator">=</span> _jobFactory<span class="token punctuation">;</span>
<span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token keyword">var</span> jobSchedule <span class="token keyword">in</span> _jobSchedules<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> job <span class="token operator">=</span> <span class="token function">CreateJob</span><span class="token punctuation">(</span>jobSchedule<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> trigger <span class="token operator">=</span> <span class="token function">CreateTrigger</span><span class="token punctuation">(</span>jobSchedule<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> Scheduler<span class="token punctuation">.</span><span class="token function">ScheduleJob</span><span class="token punctuation">(</span>job<span class="token punctuation">,</span> trigger<span class="token punctuation">,</span> cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">await</span> Scheduler<span class="token punctuation">.</span><span class="token function">Start</span><span class="token punctuation">(</span>cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">StopAsync</span><span class="token punctuation">(</span>CancellationToken cancellationToken<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">await</span> Scheduler<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">Shutdown</span><span class="token punctuation">(</span>cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> IJobDetail <span class="token function">CreateJob</span><span class="token punctuation">(</span>JobSchedule schedule<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">var</span> jobType <span class="token operator">=</span> schedule<span class="token punctuation">.</span>JobType<span class="token punctuation">;</span>
<span class="token keyword">return</span> JobBuilder
<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>jobType<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithIdentity</span><span class="token punctuation">(</span>jobType<span class="token punctuation">.</span>FullName<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithDescription</span><span class="token punctuation">(</span>jobType<span class="token punctuation">.</span>Name<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> ITrigger <span class="token function">CreateTrigger</span><span class="token punctuation">(</span>JobSchedule schedule<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> TriggerBuilder
<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithIdentity</span><span class="token punctuation">(</span>$<span class="token string">"{schedule.JobType.FullName}.trigger"</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithCronSchedule</span><span class="token punctuation">(</span>schedule<span class="token punctuation">.</span>CronExpression<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">WithDescription</span><span class="token punctuation">(</span>schedule<span class="token punctuation">.</span>CronExpression<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
Full complete solution can be found at this GitHub repo.
Hi, do you have the complete solution available for download.
Looks like many things missing from this post.
Thanks
I’ll push the source code to my git repo shortly. Will notify you when this is done. Thanks guys.
If complete solution is available for download that would be much appreciated. I find your solution clean and have a good approach.
Hi, source code for this post is now available for download. You can find the link at the bottom of this post. Cheers