<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Cecil Phillip Dev Blog]]></title><description><![CDATA[Swimmer, OCR racer, Podcaster, Music lover... and I work in Developer Relations on the side. Mostly focused on .NET, Cloud Computing, and Distributed Systems. B]]></description><link>https://cecilphillip.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 11 Apr 2026 13:55:06 GMT</lastBuildDate><atom:link href="https://cecilphillip.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Test your subscription integration through time with Stripe’s test clocks]]></title><description><![CDATA[Preface
In this series, we’ve explored some key highlights for a modest implementation of a Stripe integration that brings together the capabilities of Stripe Connect and Stripe Billing for a fictitious business, Oasis Hubs. The business provides cus...]]></description><link>https://cecilphillip.dev/test-your-subscription-integration-through-time-with-stripes-test-clocks</link><guid isPermaLink="true">https://cecilphillip.dev/test-your-subscription-integration-through-time-with-stripes-test-clocks</guid><category><![CDATA[stripe]]></category><category><![CDATA[payments]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[Aspnetcore]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Wed, 28 Feb 2024 17:19:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675659921/6ce3aea6-4b3a-4110-9b7c-dec4cf0b46f9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-preface">Preface</h2>
<p>In this series, we’ve explored some key highlights for a modest implementation of a Stripe integration that brings together the capabilities of Stripe Connect and Stripe Billing for a fictitious business, Oasis Hubs. The business provides customers access to unique private and commercial workspaces that can be conveniently booked for a given number of hours. Customers will be able to choose between different subscription tiers that will give them access to various workspace listings (hubs) which have been provided by the service vendors (hosts). Hosts will sign up to the platform to provide details of their available workspaces, and get paid monthly based on the number of hours booked for their workspace. </p>
<p>In this last (maybe?) article, we will cover validating subscription logic with test clocks. </p>
<h2 id="heading-we-should-test-this">We should test this</h2>
<p>In the software industry, and also many others, it is important that we thoroughly test our products before releasing them to the public. Unit tests, integration tests, A/B tests, performance tests, and many more aim to verify and set the quality bar so we can ship with confidence. However, with the way we test software today, there is usually an expectation that when a suite of tests is run we should receive results that we can inspect within a relatively short amount of time. Depending on the type,  tests may take a few minutes or even as long as a few hours. </p>
<p>When building support for recurring payments such as a subscription or a payment plan that might have a billing cycle spanning weeks or months, how can we verify our integration is working correctly? Waiting in real-time for one iteration of a billing cycle is far from being a viable option. While running unit tests against the various components of our software in isolation is possible, we still need the ability to run through more realistic scenarios; especially when integrating with an external payments service like Stripe.  </p>
<h2 id="heading-time-traveling-with-test-clocks">Time traveling with Test Clocks</h2>
<p>Since no one on the planet, that we know of, has successfully invented a time machine we are going to need to look for another alternative for simulating recurring billing cycles. Within the Oasis Hubs Stripe account, in test mode, we can make use of a feature called test clocks. Test clocks will allow us to simulate the forward movement of time in Stripe objects; like subscriptions, quotes, and customers. Once the time is adjusted, any time sensitive webhook events will get  triggered and we will be able to handle those events in the action methods of the WebhookController.</p>
<blockquote>
<p>We’ve discussed handling webhooks and the WebhookController on various occasions in previous articles of this series. //ADD LINK</p>
</blockquote>
<p>Test clocks can be used to validate the logic for time based scenarios in your Stripe integration such as trial periods, prorations, subscription payments, upgrades or renewals. Creating and using a test clock follows a similar pattern to the other objects we’ve created in Stripe. In this article we’ll be using the Stripe .NET library, but there are other options available such as the Stripe CLI, the Stripe Dashboard, or one of the other language SDKs. This means that instead of waiting for weeks or months to test our recurring payment integration, we can run a simulation that would only take a few minutes.</p>
<p>Before we can start a simulation, we need to create a new test clock and set its initial time using the “FrozenTime” property. We have to specify the time as a Unix Epoch timestamp which can be either in the past or in the future. Once the TestClock object is created it can only be moved forward in time.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> tcCreateOptions = <span class="hljs-keyword">new</span> TestClockCreateOptions{
   Name = <span class="hljs-string">$"Subscription Clock (<span class="hljs-subst">{name}</span>)"</span>,
   FrozenTime = DateTimeOffset.UtcNow.DateTime
};

<span class="hljs-keyword">var</span> testClockService = <span class="hljs-keyword">new</span> TestClockService();
<span class="hljs-keyword">var</span> newTestClock = <span class="hljs-keyword">await</span> testClockService.CreateAsync(tcCreateOptions);
</code></pre>
<p>Test clocks can be attached to different objects in Stripe such as Customers, Subscriptions or even quotes. The snippet above, from the Oasis Hubs fake data generator, creates a new Customer object and associates it with a test clock. Existing objects in a Stripe account cannot be later updated to use a test clock. This has to be specified at the time of creation. </p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> ccOptions = <span class="hljs-keyword">new</span> CustomerCreateOptions{
    Name = name,
    Email = email,
    Description = <span class="hljs-string">"Faker User Account"</span>,
    PaymentMethod = <span class="hljs-string">"pm_card_visa"</span>,
    TestClock = newTestClock.Id,
    InvoiceSettings = <span class="hljs-keyword">new</span> CustomerInvoiceSettingsOptions
    {
       DefaultPaymentMethod = <span class="hljs-string">"pm_card_visa"</span>
    }
};
<span class="hljs-keyword">var</span> newCustomer = <span class="hljs-keyword">await</span> customerService.CreateAsync(ccOptions);
</code></pre>
<p>Once this is done, other relevant Stripe objects linked with this Customer will also be associated with the test clock. That means when a Subscription or Invoice object is created for this Customer, we don’t have to pass the test clock Id along to them. This behavior applies to objects created via one of Stripe's hosted payment forms or if the objects created directly via the Stripe API.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> options = <span class="hljs-keyword">new</span> SubscriptionCreateOptions {
    Customer = newCustomer.Id,
    Items = <span class="hljs-keyword">new</span> List&lt;SubscriptionItemOptions&gt; {
        <span class="hljs-keyword">new</span> SubscriptionItemOptions {Price = <span class="hljs-string">"my_price_id"</span>}
    }
};

<span class="hljs-keyword">var</span> subscriptionService = <span class="hljs-keyword">new</span> SubscriptionService();
<span class="hljs-keyword">await</span> subscriptionService.CreateAsync(options);
</code></pre>
<p>Inside of the Stripe Dashboard, any Customer objects created in test mode that are associated with a test clock will have a clock item next to their record.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675656041/373d2018-14c8-4fea-a4de-2ae9c073f89a.png" alt="Customer Test Clock view" /></p>
<p>If we dig deeper into the Customer details, you will notice that we now have the option to to “Advance time” using that test clock.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675657292/95fda6ac-5066-43bb-a3e4-e5656e202eb0.png" alt="Single Customer View" /></p>
<p>Test clocks can be advanced using the UI in Stripe Dashboard and also from your application code using one of the SDKs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675658805/2bb3662f-4fbb-430f-ad7d-7c93f4520fae.png" alt="Advancing Time" /></p>
<p>However, test clocks have limitations. Test clocks can only be advanced by two intervals at a time based on the shortest subscription interval of associated items. For example, if the test clock is associated with a monthly subscription, it can only advance up to two months at a time. If there are multiple associated subscription cycles, then the clock can be advanced up to two internals of the shortest cycle. </p>
<h2 id="heading-limitations-of-time-traveling">Limitations of Time Traveling</h2>
<p>Before we start setting up and processing complex scenarios using test clocks. For each test clock created, there can be only three attached Customers, three attached Subscriptions, and ten Quotes. It is possible to create multiple test clocks that have their own unique name, initial time, and collection of associated Stripe objects. To learn more about the limitations of test clocks, take a look at documentation links referenced below.</p>
<p>Regardless of the limitations, this feature enables the possibility of some interesting test cases that you can integrate into your test automation and CI/DI pipelines.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://github.com/stripe-samples/oasis-hubs-dotnet">Oasis Hubs GitHub Repository</a></li>
<li><a target="_blank" href="https://docs.stripe.com/billing/testing/test-clocks">Stripe Test Clocks</a></li>
<li><a target="_blank" href="https://stripe.com/docs/connect">Stripe Connect</a> </li>
<li><a target="_blank" href="https://stripe.com/docs/stripe-cli">Stripe CLI</a></li>
</ul>
<h2 id="heading-stay-connected-with-stripe">Stay connected with Stripe</h2>
<p>You can also stay up-to-date with Stripe developer updates on the following platforms:</p>
<p>📣 Follow <a target="_blank" href="https://twitter.com/stripedev">@StripeDev</a> on Twitter.
📺 Subscribe to our <a target="_blank" href="https://www.youtube.com/StripeDevelopers">YouTube channel</a>.
💬 Join the official <a target="_blank" href="https://discord.com/invite/RuJnSBXrQn">Discord server</a>.
📧 Sign up for the <a target="_blank" href="https://go.stripe.global/dev-digest">Developer Digest</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing subscriptions and metered billing with Stripe in ASP.NET Core]]></title><description><![CDATA[Preface
In this series, we’ll explore some key highlights for a modest implementation of a Stripe integration that brings together the capabilities of Stripe Connect and Stripe Billing for a fictitious business, Oasis Hubs. The business provides cust...]]></description><link>https://cecilphillip.dev/implementing-subscriptions-and-metered-billing-with-stripe-in-aspnet-core</link><guid isPermaLink="true">https://cecilphillip.dev/implementing-subscriptions-and-metered-billing-with-stripe-in-aspnet-core</guid><category><![CDATA[stripe]]></category><category><![CDATA[payments]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Aspnetcore]]></category><category><![CDATA[asp.net core]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Mon, 26 Feb 2024 11:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675647364/a3f79e30-1c20-4ad8-ba99-8639e73943e3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-preface">Preface</h2>
<p>In this series, we’ll explore some key highlights for a modest implementation of a Stripe integration that brings together the capabilities of Stripe Connect and Stripe Billing for a fictitious business, Oasis Hubs. The business provides customers with access to unique private and commercial workspaces that can be conveniently booked for a given number of hours. Customers will be able to choose between different subscription tiers that will give them access to various workspace listings (hubs) which have been provided by the service vendors (hosts). Hosts will sign up to the platform to provide details of their available workspaces, and get paid monthly based on the number of hours booked for their workspace. </p>
<p>Throughout this series, we will cover topics such as setting up Stripe Connect Onboarding, managing Stripe events, implementing metered billing, and validating subscription logic with test clocks.</p>
<h2 id="heading-getting-paid">Getting Paid</h2>
<p>Oasis Hubs’ business model is based on charging for subscriptions that Customers choose from; either Basic, Standard, or Premium. Each level has its own benefits and pricing. Pricing starts at $35 monthly for Oasis Basic,  $60 monthly for Oasis Standard, and $120 monthly for Oasis Premium. Each tier comes with 10 hours of hub rental per month, but the hours do not roll over to the following months. If a customer goes over their monthly allotment, they will be charged overage fees for each additional hour.</p>
<p>What about compensating the hosts? Once customers have successfully paid their monthly invoices, the hosts get paid a percent of the total invoice relative to the number of hours a customer used in their listing. For example, if a customer used 10 hours during a given month and 5 of those hours were booked at host A’s listings then host A will get paid 50% of the available funds to be paid out.</p>
<p>Once a customer’s invoice has been paid at the end of a billing cycle, Oasis Hubs will hold on to a portion of the revenue and distribute the remaining available funds between the respective hosts.</p>
<h2 id="heading-modeling-products-and-prices-in-stripe">Modeling Products and Prices in Stripe</h2>
<p>Before we can display pricing tiers to the customers on the website, we must first model them within Stripe as Product and Price objects. Based on the description above, each tier will need to have two prices. One price will represent the fixed monthly subscription price, while the other will be the hourly rate for any accrued overages. What we’ll do is make the overage rate 10% of the monthly fixed rate. The price breakdown will look something like this.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Subscription</td><td>Fixed Monthly</td><td>Hourly Overage Rate</td></tr>
</thead>
<tbody>
<tr>
<td>Oasis Basic</td><td>$35</td><td>$3.5</td></tr>
<tr>
<td>Oasis Standard</td><td>$60</td><td>$6.0</td></tr>
<tr>
<td>Oasis Premium</td><td>$120</td><td>$12.0</td></tr>
</tbody>
</table>
</div><p>We have options when it comes to creating Products and Prices objects in Stripe. This article will focus on creating all the objects we need using the Stripe .NET library. That will give us access to the ProductService and PriceService classes that we can use to create and attach prices to products for Oasis Hubs.</p>
<pre><code class="lang-csharp">ProductService productsService = <span class="hljs-keyword">new</span> ProductService();
<span class="hljs-keyword">var</span> prodCreateOptions = <span class="hljs-keyword">new</span> ProductCreateOptions {
   Name = title,
   Description = description,
   Images = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt; { fileLink.Url },
   Features =
       features.Select(f =&gt; <span class="hljs-keyword">new</span> ProductFeatureOptions { Name = f }).ToList(),
   UnitLabel = <span class="hljs-string">"hour"</span>,
   Metadata = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>&gt; { [<span class="hljs-string">"tier.image"</span>] = imageFileName }
};

<span class="hljs-keyword">var</span> newHubProduct = <span class="hljs-keyword">await</span> productsService.CreateAsync(prodCreateOptions);
</code></pre>
<p>To create products using the ProductService, we need to populate an instance of the ProductCreateOptions object and pass it to the CreateAsync method as shown above. The only required property is “Name” but providing more detail can be helpful later on in other parts of the application. In the example above, in addition to the name, we’re adding a description, an image URL, a list of features, and the unit label.  As mentioned earlier, these properties can be updated later via the Dashboard if needed. Next, we’ll create the prices.</p>
<pre><code class="lang-csharp">PriceService priceService = <span class="hljs-keyword">new</span> PriceService();

<span class="hljs-comment">// Create flat price in product</span>
<span class="hljs-keyword">var</span> priceCreateOptions = <span class="hljs-keyword">new</span> PriceCreateOptions {
   Product = newHubProduct.Id,
   Nickname = newHubProduct.Name,
   Currency = <span class="hljs-string">"usd"</span>,
   UnitAmount = hourlyUnitPrice,
   LookupKey = <span class="hljs-string">$"<span class="hljs-subst">{priceLookupPrefix}</span>_usd"</span>,
   Recurring =
       <span class="hljs-keyword">new</span> PriceRecurringOptions { Interval = <span class="hljs-string">"month"</span>, UsageType = <span class="hljs-string">"licensed"</span> }
};

<span class="hljs-keyword">var</span> newProductPrice = <span class="hljs-keyword">await</span> priceService.CreateAsync(priceCreateOptions);

<span class="hljs-comment">// Update default price</span>
<span class="hljs-keyword">await</span> productsService.UpdateAsync(newHubProduct.Id, <span class="hljs-keyword">new</span> ProductUpdateOptions {
   DefaultPrice = newProductPrice.Id
});
</code></pre>
<p>Similar to the ProductService, with the PriceService we have to populate a PriceCreateOptions object and pass it to the CreateAsync method. This class, however, requires a currency code, a unit amount in cents which represents the prices, and also the Id of the Product object the price is associated with.</p>
<blockquote>
<p>In the Stripe object model, Products objects can have many attached Prices, but Price objects are attached to one Product. </p>
</blockquote>
<p>Since Oasis Hubs needs to have a fixed monthly price for each tier, the “Recurring” property is set on the PriceCreateOptions objects with an “Interval” of “month” and the “UsageType” set to “licensed.” In Stripe, recurring purchases can have a usage type of either “licensed” or “metered.” We can think of  “licensed” as a fixed price per quantity. For example, if we were selling a service at $15 per month per user and a company signed up with 10 employees then they would have one monthly subscription at $150 per month. We don’t need to model prices that way with Oasis Hubs, so we will always assume that this quantity for this price is one per customer. Now let’s look at creating the price for the overages.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Create tiered pricing in product</span>
priceCreateOptions = <span class="hljs-keyword">new</span> PriceCreateOptions {
   Product = newHubProduct.Id,
   Nickname = newHubProduct.Name,
   LookupKey = <span class="hljs-string">$"<span class="hljs-subst">{priceLookupPrefix}</span>_usd_tiered"</span>,
   Currency = <span class="hljs-string">"usd"</span>,
   Tiers =
       <span class="hljs-keyword">new</span> List&lt;PriceTierOptions&gt; { <span class="hljs-keyword">new</span>() { UnitAmount = <span class="hljs-number">0</span>, UpTo = <span class="hljs-number">10</span> },
                                    <span class="hljs-keyword">new</span>() { UnitAmount = hourlyUnitPrice / <span class="hljs-number">10</span>,
                                            UpTo = PriceTierUpTo.Inf } },
   Recurring =
       <span class="hljs-keyword">new</span> PriceRecurringOptions { Interval = <span class="hljs-string">"month"</span>, UsageType = <span class="hljs-string">"metered"</span> },
   TiersMode = <span class="hljs-string">"graduated"</span>,
   BillingScheme = <span class="hljs-string">"tiered"</span>
};

newProductPrice = <span class="hljs-keyword">await</span> priceService.CreateAsync(priceCreateOptions);
</code></pre>
<p>There are some significant differences between the fixed monthly Price object we created previously and the one we’re creating now. First, notice that the “UsageType” is set to “metered.” This is because the price that we’re modeling here is based on the number of additional hours consumed by a customer that month. That also means we’ll need to track usage for each customer as well, but we’ll get to that later on in the article. The next major difference to nice is that the “Tiers” property is being set with a list of pricing tiers. The first one specifies that the first 10 reported hours will not be charged. This makes sense since customers would have already paid for this allotment upfront  through the fixed monthly pricing. The second tier for the Price object specifies that each additional hour reported over the 10 initial hours will be charged at 10% of the monthly fixed rate.</p>
<h2 id="heading-processing-subscriptions">Processing Subscriptions</h2>
<p>Now that subscription products and prices have been set up in Stripe, the next thing we should probably do is create a page where customers can see them. One of the convenient options at our disposal is the <a target="_blank" href="https://stripe.com/docs/payments/checkout/pricing-table">embedded pricing table</a> from Stripe. This low code option allows us to design a pricing table with up to four products that we can embed in any website by just copying the generated code. Through the designer in the Stripe Dashboard, we can set options for tax collection,  address collection, enable promotion codes, configure the look and feel, plus many other useful options. </p>
<p>One of the limitations the embedded pricing table has is that it doesn't give us an option to require that the customer is authenticated with an active account before proceeding to a checkout page.  For this reason, Oasis Hubs created its own pricing table that’s aware of the user identity we created in the first article of this series.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675641099/9d8a661b-598d-46c5-b37c-3906e2d702e8.png" alt="Subscription Prices" /></p>
<p>To create this page, we’ll make use of the ProductService we used earlier. This time, instead of creating new products we’ll use it to query for the ones already in the Oasis Hubs Stripe account.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> IEnumerable&lt;Product&gt; HubTierListings { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } =
    Enumerable.Empty&lt;Product&gt;();

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Pricing</span>(<span class="hljs-params">UserManager&lt;OasisHubsUser&gt; userManager,
               IStripeClient stripeClient, LinkGenerator linkGenerator,
               ILogger&lt;Pricing&gt; logger</span>)</span> {
   <span class="hljs-keyword">this</span>._userManager = userManager;
   <span class="hljs-keyword">this</span>._stripeClient = stripeClient;
   <span class="hljs-keyword">this</span>._linkGenerator = linkGenerator;
   <span class="hljs-keyword">this</span>._logger = logger;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">OnGetAsync</span>(<span class="hljs-params"></span>)</span> {
   <span class="hljs-keyword">var</span> productsService = <span class="hljs-keyword">new</span> ProductService(<span class="hljs-keyword">this</span>._stripeClient);

   <span class="hljs-keyword">var</span> options =
       <span class="hljs-keyword">new</span> ProductListOptions { Expand = <span class="hljs-keyword">new</span>() { <span class="hljs-string">"data.default_price"</span> },
                                Active = <span class="hljs-literal">true</span> };

   <span class="hljs-keyword">var</span> products = <span class="hljs-keyword">await</span> productsService.ListAsync(options);
   <span class="hljs-keyword">if</span> (products.Any()) {
      HubTierListings =
          products.Where(p =&gt; p.Metadata.ContainsKey(_tierMetaKey));
   }

   <span class="hljs-keyword">return</span> Page();
}
</code></pre>
<p>This code sample above shows a snippet of what the PageModel of the pricing page looks like. We populate an instance of the ProductListOptions object and provide it to the ListAsync method of the ProductService class. The set properties let Stripe know that we only want active products and to include the default price in the result set.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"max-w-7xl m-auto px-6"</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"w-full mx-auto bg-white px-5 py-10 text-gray-600"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-center w-3/4 mx-auto"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-5xl md:text-6xl mb-5"</span>&gt;</span>Subscription Plans<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"font-medium text-xl text-gray-500 mb-10"</span>&gt;</span>Get started with one of tiered plans to get access to a curated list of our unique workspace options provided by our Oasis Hub Hosts. <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"max-w-full mx-auto md:flex"</span>&gt;</span>
         @foreach (var tier in Model.HubTierListings)
         {
            <span class="hljs-tag">&lt;<span class="hljs-name">vc:tier-pricing-item</span> <span class="hljs-attr">tier-product</span>=<span class="hljs-string">"@tier"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">vc:tier-pricing-item</span>&gt;</span>
         }
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
</code></pre>
<p>Then we’ll iterate through the results within Razor and pass each product to a custom pricing item component we created with a ViewComponent.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"w-full"</span>&gt;</span>
      @if (User.Identity?.IsAuthenticated ?? false)
      {
         <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">asp-page</span>=<span class="hljs-string">"/Pricing"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Subscribe Now<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"lookupKey"</span>
 <span class="hljs-attr">value</span>=<span class="hljs-string">"@Model.TierProduct.DefaultPrice.LookupKey"</span>/&gt;</span>
         <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
      }
      else
      {
         <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">asp-page</span>=<span class="hljs-string">"/signin"</span> <span class="hljs-attr">asp-route-returnUrl</span>=<span class="hljs-string">"/pricing"</span>&gt;</span>Sign in to Subscribe<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      }
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>The ViewComponent is mostly static but the button is where most of the interesting bits are.  If the user is signed in, we render an HTML form that kicks off the checkout process otherwise we render a button that prompts the user to “Sign in to Subscribe.” Notice that we’re using the LookupKey for the default price for the product as a hidden form field.</p>
<p>Let’s break down the handler method that processes the subscribe request.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> lookupKey = Request.Form[<span class="hljs-string">"lookupKey"</span>].ToString();

<span class="hljs-keyword">var</span> plOptions = <span class="hljs-keyword">new</span> PriceListOptions {
     LookupKeys = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt; { lookupKey, <span class="hljs-string">$"<span class="hljs-subst">{lookupKey}</span>_tiered"</span> }
};

<span class="hljs-keyword">var</span> priceService = <span class="hljs-keyword">new</span> PriceService(<span class="hljs-keyword">this</span>._stripeClient);
<span class="hljs-keyword">var</span> prices = <span class="hljs-keyword">await</span> priceService.ListAsync(plOptions);
<span class="hljs-keyword">var</span> lineItems = prices.Select(p =&gt; <span class="hljs-keyword">new</span> SessionLineItemOptions {
   Price = p.Id, Quantity = !p.LookupKey.EndsWith(<span class="hljs-string">"_tiered"</span>) ? <span class="hljs-number">1</span> : <span class="hljs-literal">null</span>
}).ToList();
</code></pre>
<p>First we retrieve the Price objects associated with the specified lookup keys. The line items here should always have two prices. One of the fixed monthly and another for the tiered usage.</p>
<pre><code class="lang-csharp"> <span class="hljs-keyword">var</span> basePageUri = _linkGenerator.GetUriByPage(<span class="hljs-keyword">this</span>.HttpContext, <span class="hljs-string">"/Index"</span>);
<span class="hljs-keyword">var</span> scOptions = <span class="hljs-keyword">new</span> SessionCreateOptions {
   Customer = user.StripeCustomerId,
   CustomerUpdate = <span class="hljs-keyword">new</span> SessionCustomerUpdateOptions { Address = <span class="hljs-string">"auto"</span> },
   LineItems = lineItems,
   Mode = <span class="hljs-string">"subscription"</span>,
   AutomaticTax = <span class="hljs-keyword">new</span> SessionAutomaticTaxOptions { Enabled = <span class="hljs-literal">true</span> },
   SuccessUrl = <span class="hljs-string">$"<span class="hljs-subst">{basePageUri}</span>/PaymentComplete?session_id={{CHECKOUT_SESSION_ID}}"</span>,
   CancelUrl = <span class="hljs-string">$"<span class="hljs-subst">{basePageUri}</span>"</span>,
   ConsentCollection = <span class="hljs-keyword">new</span>() { Promotions = <span class="hljs-string">"auto"</span> },
   AllowPromotionCodes = <span class="hljs-literal">true</span>
};

<span class="hljs-keyword">var</span> sessionService = <span class="hljs-keyword">new</span> SessionService(<span class="hljs-keyword">this</span>._stripeClient);
<span class="hljs-keyword">var</span> session = <span class="hljs-keyword">await</span> sessionService.CreateAsync(scOptions);
<span class="hljs-keyword">return</span> Redirect(session.Url);
</code></pre>
<p>Then we create a new checkout session for the logged in user. Following a similar pattern as other Stripe services, we create an instance of the SessionCreateOptions object and pass it on to the CreateAsync method of the SessionService class. The session we’re creating will let Stripe know that we want to provision a hosted payment form for our logged in user with the given products; in this case it’s an Oasis Subscription. Some essential properties to pay attention to here are “Customer,” “Mode,” and “LineItems.” Also notice the SuccessUrl and CancelUrl properties which are used to redirect customers back to the main Oasis Hubs website to different URLs depending on if they successfully completed the checkout form or not.</p>
<p>After creating the checkout session, we can redirect the customer to the hosted checkout page using the URL property.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675642379/d6b83bf8-e27b-478c-9266-fb39af314ecc.png" alt="Subscription Page" /></p>
<p>After the customer completes checkout, they’ll get redirected to the URL set on the SuccessUrl property of the session. As the payment is being processed, Stripe will generate a series of events which can be observed and processed using our webhook handler we created using the WebhooksController in the previous article of this series. </p>
<pre><code class="lang-csharp"><span class="hljs-keyword">switch</span> (stripeEvent.Type) {
<span class="hljs-keyword">case</span> Events.InvoicePaid:
{
  <span class="hljs-keyword">var</span> invoice = (stripeEvent.Data.Object <span class="hljs-keyword">as</span> Invoice) ! ;
      <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>._commandProcessor.PostAsync(<span class="hljs-keyword">new</span> InitiateFundsTransferCommand(invoice));
  <span class="hljs-keyword">break</span>;
  }
<span class="hljs-keyword">case</span> Events.CustomerSubscriptionCreated:
{
  <span class="hljs-keyword">var</span> newSubscription = (stripeEvent.Data.Object <span class="hljs-keyword">as</span> Subscription) ! ;
    <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>._commandProcessor.PostAsync(
      <span class="hljs-keyword">new</span> ActivateCustomerSubscriptionCommand(newSubscription));
    <span class="hljs-keyword">break</span>;
  }
<span class="hljs-keyword">case</span> Events.CustomerSubscriptionUpdated:
{
  <span class="hljs-keyword">var</span> updatedSubscription = (stripeEvent.Data.Object <span class="hljs-keyword">as</span> Subscription) ! ;
   <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>._commandProcessor.PostAsync(
      <span class="hljs-keyword">new</span> ActivateCustomerSubscriptionCommand(updatedSubscription));
  <span class="hljs-keyword">break</span>;
  }
<span class="hljs-keyword">default</span>:
  _logger.LogInformation(<span class="hljs-string">"Unhandled event type: {StripeEvent}"</span>, stripeEvent.Type);
  <span class="hljs-keyword">break</span>;
}
</code></pre>
<p>Some of the events we should pay attention to include customer.subscription.created, customer.subscription.deleted, customer.subscription.paused and customer.subscription.updated. Here is where we can start any associated workflows, adjust user permissions, or update any data tables related to the customer’s account. One of the workflows for Oasis Hubs sets the ActiveSubscriptionId and HasSubscriptionActive properties on the OasisHubUser record for the customer. We introduced this type in the first article of this series.</p>
<h2 id="heading-subscription-management">Subscription Management</h2>
<p>It is fair to assume that, after subscribing, customers might want to make adjustments to their subscriptions. They will expect to be able to update their billing information, view their invoice history, upgrade their subscriptions, or even cancel their subscriptions entirely. As responsible engineers, we should build these types of capabilities into our applications. But what if we don’t have too?</p>
<p>If we prefer not to spend the engineering cycles on this type of work, Stripe’s Customer Portal can provide us with a low-code option for adding these features to our applications.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675643615/ce14b138-6abd-499e-b5df-7f6c86c4e8ef.png" alt="Customer Portal" /></p>
<p>To enable the Customer Portal, go to the Stripe Dashboard, use the search bar to search for the setting, then click on the “Enable” button on the configuration page. We will be able to toggle features on or off to suit our needs. To incorporate the portal into an application, we’ll have to create a new session for the customer and redirect them to using the generated URL. This is very similar to the checkout session we created earlier.</p>
<pre><code class="lang-csharp">   [<span class="hljs-meta">Authorize</span>]
   [<span class="hljs-meta">HttpPost(<span class="hljs-meta-string">"create-portal-session"</span>)</span>]
   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">CreatePortalSession</span>(<span class="hljs-params"></span>)</span> {
      <span class="hljs-keyword">var</span> user = <span class="hljs-keyword">await</span> _userManager.GetUserAsync(User);
      <span class="hljs-keyword">if</span> (user == <span class="hljs-literal">null</span>) {
         <span class="hljs-keyword">return</span> RedirectToPage(<span class="hljs-string">"/SignIn"</span>);
      }

      <span class="hljs-keyword">var</span> basePageUri = _linkGenerator.GetUriByPage(<span class="hljs-keyword">this</span>.HttpContext, <span class="hljs-string">"/Index"</span>);
      <span class="hljs-keyword">var</span> options = <span class="hljs-keyword">new</span> Stripe.BillingPortal.SessionCreateOptions
      {
         Customer = user.StripeCustomerId,
         ReturnUrl = basePageUri
      };
      <span class="hljs-keyword">var</span> service = <span class="hljs-keyword">new</span> Stripe.BillingPortal.SessionService(_stripeClient);
      <span class="hljs-keyword">var</span> portalSession = <span class="hljs-keyword">await</span> service.CreateAsync(options);
      <span class="hljs-keyword">return</span> Redirect(portalSession.Url);
   }
</code></pre>
<blockquote>
<p>It’s important that these sessions are only shared with authenticated users since it exposes the ability to view and update sensitive information like the customer’s billing address and payment information.</p>
</blockquote>
<p>To create a portal session, we’ll need the Id for the Stripe Customer object and a return URL the customer should be redirected when they’re ready to leave the portal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675644737/5e438c43-f7ed-42b4-9e47-77c4ce0a1f74.png" alt="Customer Portal" /></p>
<p>Once a customer is redirected to the portal, they’ll be able to manage their subscription details through a Stripe hosted page.</p>
<h2 id="heading-reporting-usage">Reporting Usage</h2>
<p>At this stage, the Oasis Hubs website is set up to process subscriptions so the missing piece in our metered billing story is tracking the hours used when customers book workspaces.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675646027/ca9b2c8b-87f6-43ee-b920-16521fa8cfe4.png" alt="Hub listing" /></p>
<p>For the purpose of keeping the demo relatively simple, collect usage at the time of booking. In a more realistic scenario we’ll want to implement a more robust check in/out system. On the details page for a workspace listing, provide a simple form where customers can enter a check in date, number of guests, and how many hours they want to use. The handler method on our Razor Page handles the request processing. </p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> checkInDate = DateTimeOffset.Parse(Request.Form[<span class="hljs-string">"checkInDate"</span>].ToString());

<span class="hljs-keyword">var</span> hours = <span class="hljs-keyword">int</span>.Parse(Request.Form[<span class="hljs-string">"hours"</span>].ToString());
<span class="hljs-keyword">var</span> booking = <span class="hljs-keyword">new</span> Booking {
  RenterId = renterId,
  RentalId = Request.Form[<span class="hljs-string">"rentalId"</span>].ToString(),
  Hours = hours,
  ReservedDateUtc = checkInDate.ToUniversalTime()
};

<span class="hljs-keyword">await</span> <span class="hljs-keyword">using</span>
<span class="hljs-keyword">var</span> context = <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>._dbContextFactory.CreateDbContextAsync();
context.Bookings.Add(booking);
<span class="hljs-keyword">await</span> context.SaveChangesAsync();
</code></pre>
<p>Once the request has been validated, create a new record in the bookings table of our database using Entity Framework Core.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// retrieve subscription</span>
<span class="hljs-keyword">var</span> subscriptionService = <span class="hljs-keyword">new</span> SubscriptionService(<span class="hljs-keyword">this</span>._stripeClient);
<span class="hljs-keyword">var</span> subscription = <span class="hljs-keyword">await</span> subscriptionService.GetAsync(OasisUser.ActiveSubscriptionId);

  <span class="hljs-keyword">var</span> subItem = subscription.Items.Data.Single(s =&gt;s.Price.LookupKey.EndsWith(<span class="hljs-string">"_tiered"</span>));

  <span class="hljs-comment">// report on usage</span>
  <span class="hljs-keyword">var</span> ucOptions = <span class="hljs-keyword">new</span> UsageRecordCreateOptions {
    Quantity = hours,
    <span class="hljs-comment">//Timestamp = DateTime.UtcNow,</span>
    Action = <span class="hljs-string">"increment"</span>
  };
  <span class="hljs-keyword">var</span> idempotencyKey = Guid.NewGuid().ToString(<span class="hljs-string">"N"</span>);
  <span class="hljs-keyword">var</span> requestOptions = <span class="hljs-keyword">new</span> RequestOptions {
    IdempotencyKey = idempotencyKey
  };

  <span class="hljs-keyword">var</span> usageRecordService = <span class="hljs-keyword">new</span> UsageRecordService(<span class="hljs-keyword">this</span>._stripeClient);
  <span class="hljs-keyword">await</span> usageRecordService.CreateAsync(subItem.Id, ucOptions, requestOptions);
</code></pre>
<p>To report the customer’s usage for the subscription, make use of two additional service classes from Stripe .NET; SubscriptionService and UsageRecordService. </p>
<p>Using the ActiveSubscriptionId property of our logged in user, we retrieve the Subscription object from Stripe with the SubscriptionService. If you recall, each subscription has two prices, but here we’re only interested in the tiered price. Then we create a usage report by populating an instance of the UsageReportCreateOptions object with the number of hours being booked and setting the action to “increment”. We supply the UsageRecordService with the UsageReportCreateOptions instance and the Id of the subscription item that represents the tiered price.</p>
<p>At the end of the billing cycle, Stripe will automatically finalize the invoice and charge the saved payment method for the customer. As you can imagine, this will generate a variety of events that can be subscribed to and handled via webhooks. As shown in an image earlier in this article, one of the events Oasis Hubs is particularly interested in is the “invoice.paid” event. Here we can refresh the customer status if necessary and distribute funds to our hosts. </p>
<h2 id="heading-paying-the-connected-accounts">Paying the connected accounts</h2>
<p>We have previously discussed the registration flow hosts onboarding with Oasis Hubs. They are associated with regular Customer objects in Stripe as well as Stripe Connect Express accounts. When hosts create listings for workspaces, a database record is created that contains a field for the Id of the Connect account it’s associated with.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HubRental</span> {
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = Guid.NewGuid().ToString(<span class="hljs-string">"N"</span>);
   <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">string</span>.Empty;
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">string</span>.Empty;
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Location { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">string</span>.Empty;
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Capacity { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
   <span class="hljs-keyword">public</span> RentalType HubType { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = RentalType.Other;
   <span class="hljs-keyword">public</span> RentalTier HubTier { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = RentalTier.Basic;
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> ImageUrl { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">string</span>.Empty;
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> StripeAccountId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">string</span>.Empty;
   <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> ReferenceCode { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">string</span>.Empty;
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsActive { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">true</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> RentalTier { Basic, Standard, Premium }

<span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> RentalType { EntireSpace,Room, Desk, Other }
</code></pre>
<p>When the “invoice.paid” event is received, we publish a message to the message broker (RabbitMQ) that instructs our system to start the payment distribution process for the Connect accounts. Within the process, we retrieve the customer, workspace listing, and booking records from the database. Then we do some math to determine how much money each account is owed. After that, we initiate a transfer using Stripe .NET.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> transOptions = <span class="hljs-keyword">new</span> TransferCreateOptions {
  Amount = transferAmount,
  Currency = <span class="hljs-string">"usd"</span>,
  Destination = accountId,
  SourceTransaction = command.Invoice.ChargeId,
  Metadata = <span class="hljs-keyword">new</span> Dictionary &lt; <span class="hljs-keyword">string</span>,
  <span class="hljs-keyword">string</span> &gt; { [<span class="hljs-string">"invoice.id"</span>] = command.Invoice.InvoiceId,
    [<span class="hljs-meta"><span class="hljs-meta-string">"invoice.hours.total"</span></span>] = totalReportedHours.ToString(CultureInfo.InvariantCulture),
    [<span class="hljs-meta"><span class="hljs-meta-string">"invoice.hours.account_reported"</span></span>] = reportedAccountHours.ToString(CultureInfo.InvariantCulture),
    [<span class="hljs-meta"><span class="hljs-meta-string">"invoice.hours.percentage"</span></span>] = accountHoursPercentage.ToString(CultureInfo.InvariantCulture)
  }
};

<span class="hljs-keyword">await</span> transferService.CreateAsync(transOptions, cancellationToken: cancellationToken);
</code></pre>
<p>I left out the math part. That’s pretty boring anyway and you didn’t come here for that. However, the snippet above shows the code required to tell Stripe to begin the transfer process. We create a TransferCreatedOptions instance and provide it with the amount, currency, and the account Id of the Connect account that the funds should be sent to. For record keeping purposes, we also assign the “SourceTransaction” property so we can know what invoice payment these transferred funds are associated with.</p>
<p>At this point, the Oasis Hubs integration with Stripe has most of the functionality needed to run the business. Customers and hosts are able to sign up and go through onboarding. The subscriptions have been set up using Products and Prices objects. Customers can choose a subscription and report the hours they’ve used. Then we just saw how to pay hosts using the TransferService in Stripe .NET. In the next article of this series, we learn how to test out our subscription logic using test clocks.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://github.com/stripe-samples/oasis-hubs-dotnet">Oasis Hubs GitHub Repository</a></li>
<li><a target="_blank" href="https://stripe.com/docs/connect">Stripe Connect</a> </li>
<li><a target="_blank" href="https://stripe.com/docs/connect/explore-connect-guide">Stripe Connect guide</a></li>
<li><a target="_blank" href="https://stripe.com/docs/connect/accounts">Stripe Connect Account Types</a></li>
<li><a target="_blank" href="https://stripe.com/docs/stripe-cli">Stripe CLI</a></li>
</ul>
<h2 id="heading-stay-connected-with-stripe">Stay connected with Stripe</h2>
<p>You can also stay up-to-date with Stripe developer updates on the following platforms:</p>
<p>📣 Follow <a target="_blank" href="https://twitter.com/stripedev">@StripeDev</a> on Twitter.
📺 Subscribe to our <a target="_blank" href="https://www.youtube.com/StripeDevelopers">YouTube channel</a>.
💬 Join the official <a target="_blank" href="https://discord.com/invite/RuJnSBXrQn">Discord server</a>.
📧 Sign up for the <a target="_blank" href="https://go.stripe.global/dev-digest">Developer Digest</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Managing Webhook Events for Connected Accounts]]></title><description><![CDATA[Preface
In this series of blog posts, we’ll explore some key highlights for a modest implementation of a Stripe integration that brings together the capabilities of Stripe Connect and Stripe Billing for a fictitious business, Oasis Hubs. 
The busines...]]></description><link>https://cecilphillip.dev/managing-webhook-events-for-connected-accounts</link><guid isPermaLink="true">https://cecilphillip.dev/managing-webhook-events-for-connected-accounts</guid><category><![CDATA[stripe]]></category><category><![CDATA[payment]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[Aspnetcore]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Thu, 22 Feb 2024 13:15:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675630566/f7bdb95a-8ecb-4d29-8db8-2766a7e67cce.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-preface">Preface</h2>
<p>In this series of blog posts, we’ll explore some key highlights for a modest implementation of a Stripe integration that brings together the capabilities of Stripe Connect and Stripe Billing for a fictitious business, Oasis Hubs. </p>
<p>The business provides customers with access to unique private and commercial workspaces that can be conveniently booked by the hour. Customers will be able to choose between different subscription tiers that will give them access to various workspace listings (hubs) which have been provided by service vendors (hosts). Hosts will sign up to the platform to provide details of their available workspaces, and get paid monthly based on the number of hours booked for their workspace.</p>
<h2 id="heading-beyond-apis">Beyond APIs</h2>
<p>In the previous article in this series, we examined how to integrate customer registration and onboarding for Stripe Connect accounts into an ASP.NET Core Razor Page application. To do this, we leveraged the <a target="_blank" href="https://www.nuget.org/packages/Stripe.net">Stripe .NET NuGet package</a> which provides a convenient wrapper around the Stripe HTTP APIs. While Stripe does provide an abundance of functionality through API endpoints, there will be situations that require more than a typical request-response interaction. </p>
<p>If we consider some of the typical workflows that happen in the world of business, we understand that there are some processes that can resolve themselves immediately while others might take more time to complete. The financial workflows supported by Stripe also operate in a very similar fashion. Invoking commands to create new customers or products within a Stripe account have the expectation of executing and returning a result almost immediately. On the other hand, carrying out tasks like identity verification or processing delayed payment methods might take minutes, hours or even days. Take a moment and consider the onboarding process of Oasis Hubs in the real world. Verifying those business and banking details for Connect accounts can take some time to complete. So how can we handle these types of interactions? How do we get notified and react to updates that happen to the processes within our Stripe account? This is where we have to go beyond APIs and start thinking about events.</p>
<h2 id="heading-understanding-stripe-events">Understanding Stripe Events</h2>
<p>As updates happen to the state of the activities running within your Stripe account, various events get generated to inform you that something has changed. This can be extremely beneficial for Stripe integrations as they can subscribe to the events they’re interested in and run the associated workflows. For example, imagine subscribing to an event that lets you know another order was placed for the custom t-shirts your company makes. At this point, you might want to start your fulfillment process of packing and shipping the new order to the customer. The pricing model of Oasis Hubs is based on monthly subscription. It will be important to listen for created and canceled subscription events to know access to listings should be updated for a given user.</p>
<p>When events are triggered in a Stripe account, a new Event object will be created that contains contextual information about what caused the event. The API reference documentation contains an exhaustive list of <a target="_blank" href="https://stripe.com/docs/api/events/types">event types</a> that it supports. While events vary, there are a few key properties on each one that should be paid attention to. </p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"evt_1NG8Du2eZvKYlo2CUI79vXWy"</span>,
  <span class="hljs-attr">"object"</span>: <span class="hljs-string">"event"</span>,
  <span class="hljs-attr">"api_version"</span>: <span class="hljs-string">"2019-02-19"</span>,
  <span class="hljs-attr">"created"</span>: <span class="hljs-number">1686089970</span>,
  <span class="hljs-attr">"data"</span>: {   },
  <span class="hljs-attr">"livemode"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"pending_webhooks"</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">"request"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-literal">null</span>,
    <span class="hljs-attr">"idempotency_key"</span>: <span class="hljs-literal">null</span>
  },
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"customer.subscription.created"</span>
}
</code></pre>
<p>The image above shows a condensed version of what an event object might look like. Probably the most important property of them all is “type”. You will need to inspect this value to determine which event has been triggered; which also has implications for the type of data it’s carrying. Another important property to observe, as you might have guessed, is “data”. This contains the Stripe object that is most relevant to the event that just fired. If you’ve subscribed to and received a customer.updated event, it would be fair to expect that the data property of the event could contain a customer object. The “api_version” property of the event object indicates the version of the Stripe API that the payload is associated with. This can be important when debugging your integration as different versions return payloads with different properties which could cause issues if your installed SDK or parser is expecting a particular shape.</p>
<blockquote>
<p>To learn more about setting an API version in your SDK and on your Stripe account, take a look at the following link https://stripe.com/docs/libraries/set-version</p>
</blockquote>
<h2 id="heading-implementing-webhooks">Implementing Webhooks</h2>
<p>The way an application can subscribe to event activity firing on a Stripe account is through registering a webhook. This is a common pattern used by service platforms to notify customers via an exposed HTTP endpoint. For the security minded folks, this really should be a HTTPS endpoint and that is a requirement for live mode environments in Stripe. I like to think of webhooks as an implementation of the Hollywood Principle, "Don't Call Us, We'll Call You".</p>
<p>Registering a webhook endpoint with Stripe can be done through the dashboard, but what we will be doing instead is seeing how to run webhook handlers locally using the Stripe CLI. In either case, you have the option to choose the events your integration should subscribe to. There are many different events that can be triggered and receiving them all can easily overwhelm your system.</p>
<p>To create a webhook handler in ASP.NET Core, we will need to add a method to the Oasis Hubs project that can respond to HTTP POST requests and deserialize the Stripe event object contained in the request body. The code sample below shows an action method for a Controller but feel free to use minimal APIs or whatever your preference might be for handling HTTP requests.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">HttpPost(<span class="hljs-meta-string">"stripe/platform"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">PlatformHandler</span>(<span class="hljs-params"></span>)</span> {
  <span class="hljs-keyword">var</span> payload = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> StreamReader(Request.Body).ReadToEndAsync();
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">var</span> stripeEvent = EventUtility.ConstructEvent(
        payload, Request.Headers[<span class="hljs-string">"Stripe-Signature"</span>],
        <span class="hljs-keyword">this</span>._stripeConfiguration.GetValue&lt;<span class="hljs-keyword">string</span>&gt;(<span class="hljs-string">"WebhookSecret"</span>));

    <span class="hljs-keyword">switch</span> (stripeEvent.Type) {
      <span class="hljs-keyword">case</span> Events.InvoicePaid: {
        <span class="hljs-keyword">var</span> invoice = (stripeEvent.Data.Object <span class="hljs-keyword">as</span> Invoice)!;

        <span class="hljs-keyword">this</span>._logger.LogDebug(
            <span class="hljs-string">"Initiating funds transfer for paid invoice ({InvoiceId})"</span>,
            invoice.Id);

        <span class="hljs-keyword">break</span>;
      }
      <span class="hljs-keyword">default</span>:
        _logger.LogInformation(<span class="hljs-string">"Unhandled event type: {StripeEvent}"</span>,
                               stripeEvent.Type);
        <span class="hljs-keyword">break</span>;
    }

    <span class="hljs-keyword">return</span> Ok();
  } <span class="hljs-keyword">catch</span> (StripeException ex) {
    _logger.LogError(ex, <span class="hljs-string">"There was an issue processing this webhook request"</span>);
    <span class="hljs-keyword">return</span> BadRequest();
  }
}
</code></pre>
<p>Regardless of the language or framework used, most webhook handlers will follow in a similar sequence. First, attach a method to the route at which you’ll want to receive event notifications. Retrieve the event object from the request body and parse the JSON. Since these routes are usually publicly accessible, you will want to validate the request to make sure that it’s actually coming from Stripe. The Stripe.net library comes with a helpful utility that does just that. The EventUtility.ConstructEvent method will validate and deserialize the Event object of the webhook request if you pass it the JSON payload, the value of the Stripe-Signature request header, and the webhook secret associated with your Stripe account.</p>
<blockquote>
<p>In addition to request validation, production implementations might also want to consider adding rate limiting or an allow/block list.</p>
</blockquote>
<p>Now that you have a strongly typed Event object, you can inspect the Type property, look for any of the event types your integration is concerned with and run any applicable workflows. Notice how the Data.Object property can be cast to the Stripe object associated with the event type.</p>
<p>When creating your webhook handler, there are some behavioral constraints that you should be aware of. For instance, handlers should quickly return a successful (HTTP 200) response once an event is received. Executing long running tasks within the body of your handler is not recommended as that may cause timeouts and Stripe will attempt to resend the event. This retry behavior involves an exponential backoff but does vary between live and test modes. Because of this, it is a good idea for you to implement some logic for deduplicating events or making your event processing idempotent. </p>
<p>A common solution for scaling webhook event processing is to make use of an external queuing system like Azure Service Bus or RabbitMQ. When events are received and validated, the webhook handler will publish a message to a queue on the message broker, and another process  listening to that queue can start processing the messages. This allows the webhook handler to do very little work and return quickly. This also enables some scalability as additional listener processes can be added if queued messages need to be processed more quickly. In the .NET space, there are many options to choose from when it comes to message processing frameworks. MassTransit, NServiceBus, and Wolverine are some interesting examples. In the codebase for Oasis Hubs, we’re using an OSS messaging framework called Brighter along with RabbitMQ as the message broker. </p>
<p>When working with subscription based payments, there are many events that get  triggered for a business to pay attention to. A few of them include:</p>
<ul>
<li>customer.subscription.created - triggered when a new subscription gets created</li>
<li>customer.subscription.deleted - triggered when a subscription ends</li>
<li>invoice.created - triggered when a new invoice gets created for a payment period</li>
<li>invoice.paid - triggered when the invoice is successfully paid</li>
<li>invoice.payment_failed - triggered when a payment attempt for an invoice fails</li>
</ul>
<p>This is far from a complete list of the events that are involved in a subscription, but also keep in mind that a typical integration might only need to address a handful of these. For Oasis Hubs, we specifically focus on the customer.subscription.created, customer.subscription.updated and invoice.paid events.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">switch</span> (stripeEvent.Type) {
  <span class="hljs-keyword">case</span> Events.InvoicePaid: {
    <span class="hljs-keyword">var</span> invoice = (stripeEvent.Data.Object <span class="hljs-keyword">as</span> Invoice)!;

    <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>._commandProcessor.PostAsync(
        <span class="hljs-keyword">new</span> InitiateFundsTransferCommand(invoice));
    <span class="hljs-keyword">break</span>;
  }
  <span class="hljs-keyword">case</span> Events.CustomerSubscriptionCreated: {
    <span class="hljs-keyword">var</span> newSubscription = (stripeEvent.Data.Object <span class="hljs-keyword">as</span> Subscription)!;

    <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>._commandProcessor.PostAsync(
        <span class="hljs-keyword">new</span> ActivateCustomerSubscriptionCommand(newSubscription));

    <span class="hljs-keyword">break</span>;
  }
  <span class="hljs-keyword">case</span> Events.CustomerSubscriptionUpdated: {
    <span class="hljs-keyword">var</span> updatedSubscription = (stripeEvent.Data.Object <span class="hljs-keyword">as</span> Subscription)!;

    <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>._commandProcessor.PostAsync(
        <span class="hljs-keyword">new</span> ActivateCustomerSubscriptionCommand(updatedSubscription));

    <span class="hljs-keyword">break</span>;
  }
  <span class="hljs-keyword">default</span>:
    _logger.LogInformation(<span class="hljs-string">"Unhandled event type: {StripeEvent}"</span>,
                           stripeEvent.Type);
    <span class="hljs-keyword">break</span>;
}
</code></pre>
<p>The sample above shows a slimmed down version of the actual implementation, but the core pattern is the same. Check the event type, cast the event object to its actual type, and then send a message to the queue.  Instead of having to work directly with strings, the Stripe .NET library provides an Events class that contains constant string representations for all the event types. It’s definitely saved me time from having to debug typos in my webhook handlers. </p>
<p>To locally test your webhook integration, the Stripe CLI has two commands that are particularly useful. When running a project on localhost, you’ll want to be able to capture Stripe events in your local instance so you can inspect things with your debugging tools. In the command line, you can run “stripe listen --forward-to ”, supplying it with the local URL to forward the test mode events to. Here’s an example using the CLI command with the action method shown earlier.</p>
<pre><code class="lang-shell">stripe listen --forward-to http://localhost:5000/api/webhooks/platform
</code></pre>
<p>Now that you have a listener, you’ll want to trigger some test events. You can do this by interacting with objects through Stripe Dashboard or by triggering them manually using the CLI trigger command.</p>
<pre><code class="lang-shell">stripe trigger invoice.paid
</code></pre>
<h2 id="heading-connected-events">Connected Events</h2>
<p>Working with Stripe Connect accounts adds an interesting dynamic to handling events. If the main Stripe account has multiple connected accounts attached to it, each with their own activities occurring, how can we know who the trigger event belongs to? Luckily, we already have 90% of the setup already done. When registering your webhooks in the Stripe Dashboard, set the listen to option to “Events on Connected accounts”. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675629458/72487c57-d1d3-4ea8-bffd-aa9b9efeda0a.png" alt="Configure webhooks" /></p>
<p>With the Stripe CLI, you can add the “--forward-connect-to” switch to the listen command.</p>
<pre><code class="lang-shell">stripe listen --forward-to http://localhost:5000/api/webhooks/platform --forward-connect-to  http://localhost:5000/api/webhooks/connect
</code></pre>
<p>A recommended approach, and how Oasis Hubs has been integrated, is to have separate webhook endpoints for regular account events and Connect events. This greatly helps with simplifying the logic of your handlers and makes the code more readable. In Oasis Hubs, for example, we look out for the account.updated events to get notified when a new connect account successfully completes onboarding. You can also look out for the account.application.deauthorized as well to know when a Connect account disconnects from your platform.</p>
<p>When handling Connect events, it is expected that there will be multiple Connect accounts associated with your application so it is important to know which account triggered the event. Conveniently, each event generated by a Connect account also contains a top-level “account” property that contains the id for the source account.  Now, any actions run in response to Connect events will be able to operate in the correct context. </p>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://github.com/stripe-samples/oasis-hubs-dotnet">Oasis Hubs GitHub Repository</a></li>
<li><a target="_blank" href="https://stripe.com/docs/connect">Stripe Connect</a> </li>
<li><a target="_blank" href="https://stripe.com/docs/connect/explore-connect-guide">Stripe Connect guide</a></li>
<li><a target="_blank" href="https://stripe.com/docs/connect/accounts">Stripe Connect Account Types</a></li>
<li><a target="_blank" href="https://stripe.com/docs/stripe-cli">Stripe CLI</a></li>
</ul>
<h2 id="heading-stay-connected-with-stripe">Stay connected with Stripe</h2>
<p>You can also stay up-to-date with Stripe developer updates on the following platforms:</p>
<p>📣 Follow <a target="_blank" href="https://twitter.com/stripedev">@StripeDev</a> on Twitter.
📺 Subscribe to our <a target="_blank" href="https://www.youtube.com/StripeDevelopers">YouTube channel</a>.
💬 Join the official <a target="_blank" href="https://discord.com/invite/RuJnSBXrQn">Discord server</a>.
📧 Sign up for the <a target="_blank" href="https://go.stripe.global/dev-digest">Developer Digest</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Onboarding Stripe Connect Express accounts in ASP.NET Core]]></title><description><![CDATA[Preface
In this series of blog posts, we’ll explore some key highlights for a modest implementation of a Stripe integration that brings together the capabilities of Stripe Connect and Stripe Billing for a fictitious business, Oasis Hubs.
This "Airbnb...]]></description><link>https://cecilphillip.dev/onboarding-stripe-connect-express-accounts-in-aspnet-core</link><guid isPermaLink="true">https://cecilphillip.dev/onboarding-stripe-connect-express-accounts-in-aspnet-core</guid><category><![CDATA[stripe]]></category><category><![CDATA[payments]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Tue, 20 Feb 2024 18:12:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675601351/f43cf062-04a4-433a-87a7-8ff616fbcce5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-preface">Preface</h2>
<p>In this series of blog posts, we’ll explore some key highlights for a modest implementation of a Stripe integration that brings together the capabilities of Stripe Connect and Stripe Billing for a fictitious business, Oasis Hubs.</p>
<p>This "Airbnb for co-working spaces" business provides customers with access to unique private and commercial workspaces that can be conveniently booked for a given number of hours. Customers will be able to choose between different subscription tiers that will give them access to various workspace listings (hubs) which have been provided by the service vendors (hosts). Hosts will sign up to the platform to provide details of their available workspaces, and get paid monthly based on the number of hours booked for their workspace.</p>
<p>Throughout this series, we will cover topics such as setting up Stripe Connect Onboarding, managing Stripe events, implementing metered billing, and validating subscription logic with test clocks. You can find a link to the demo code for this series in the reference <a class="post-section-overview" href="#references">links below</a>.</p>
<h2 id="heading-get-connected">Get Connect(ed)</h2>
<p>Before hosts can begin posting their workspace listings and getting paid for bookings, they first need to get signed up for an Oasis Hubs account and then go through host onboarding. For sign ups, we’ll see how to make use of <a target="_blank" href="https://learn.microsoft.com/aspnet/core/security/authentication/identity">ASP.NET Core Identity</a> with a custom model to store some additional data.</p>
<p>What about onboarding though? Hosts need to be able to connect their business to Oasis Hubs in a compliant way before they can start getting paid for the usage of their workspaces. Since Oasis Hubs is already using Stripe to handle payments, we’ll use Stripe Connect to connect (see what I did there) the hosts with Oasis Hubs’ Stripe account so that payments from customers and to hosts can be handled in the same platform.</p>
<p>If you are unfamiliar with <a target="_blank" href="https://stripe.com/docs/connect">Stripe Connect</a>, you can think of it as a solution that allows businesses to process payments and route those payments between multiple providers. This makes Stripe Connect an interesting choice for businesses that operate marketplaces or offer franchising opportunities. For our use case, the hosts will be the service providers for Oasis Hubs. As payments are processed by the main business, various amounts can be distributed between the respective providers.</p>
<p>There are a few things that need to happen first before Oasis Hubs, or even your own business, can start their Stripe Connect integration. Through the Stripe dashboard:</p>
<ul>
<li><p>Make sure the Stripe account has been <a target="_blank" href="https://dashboard.stripe.com/account/onboarding">activated</a></p>
</li>
<li><p><a target="_blank" href="https://dashboard.stripe.com/connect/tasklist">Register</a> the Stripe account for Connect</p>
</li>
<li><p>Optionally, configure a <a target="_blank" href="https://stripe.com/docs/connect/explore-connect-guide#pay-users">payout schedule</a></p>
</li>
</ul>
<p>A host will have to be associated with one of three Stripe Connect account types; Standard, Express and Custom. <a target="_blank" href="https://stripe.com/docs/connect/accounts">Choosing between one account type versus another</a> really depends on the experience you want your users to have and also the available engineering resources to execute the integration. As you can probably tell from the title, this article and the rest of the series will focus on the usage of Express accounts. With this option, your integration effort is fairly low and Stripe handles all of the onboarding, account management, and identity verification for Oasis Hubs. This allows Stripe to gather any KYC (Know Your Customer) requirements enforced by global financial regulators. However, this choice also means that Oasis Hubs will have to be responsible for handling refunds, disputes, and providing customer support. If you’re interested in learning more about the other account types, I recommend taking a look at some of the references linked at the end of this article.</p>
<h2 id="heading-signing-up">Signing up</h2>
<p>For some background, Oasis Hubs is built on .NET 7 using a combination of ASP.NET Core <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/razor-pages/">Razor Pages</a>, <a target="_blank" href="https://learn.microsoft.com/en-us/ef/core/">Entity Framework Core</a>, <a target="_blank" href="https://tailwindcss.com/">Tailwind UI</a> and some other interesting components that will be explored across this series. As mentioned earlier, ASP.NET Core Identity needs to be set up to handle the customer sign ups so the following NuGet packages will need to be added to the project.</p>
<ul>
<li><p>Microsoft.AspNetCore.Identity.EntityFrameworkCore</p>
</li>
<li><p>Microsoft.EntityFrameworkCore.SqlServer</p>
</li>
<li><p>Stripe .NET</p>
</li>
</ul>
<p>Once the packages are installed, we will move forward with creating the custom model for the user. ASP.NET Core Identity comes bundled with an IdentityUser class that has some predetermined fields like UserName, Email, PhoneNumber, etc. In Oasis Hubs, the same user type will be used for both hosts and customers. In the image below, OasisHubUser extends IdentityUser with fields that will be used later on.</p>
<pre><code class="lang-csharp">
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OasisHubsUser</span> : <span class="hljs-title">IdentityUser</span> {
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> StripeCustomerId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> StripeAccountId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> ActiveSubscriptionId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsHost { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> HasSubscriptionActive { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsEnabled { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">virtual</span> ICollection&lt;IdentityUserClaim&lt;<span class="hljs-keyword">string</span>&gt;&gt; Claims { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>Here is a listing of the custom properties that will need to be added to the model. Their usage will be covered as we progress through the series.</p>
<ul>
<li><p>StripeCustomerId - Id for the Stripe Customer object associated with this user</p>
</li>
<li><p>StripeAccountId - Id of the Stripe Express account for a host. Shouldn’t be set for a regular user</p>
</li>
<li><p>ActiveSubscriptionId - Id of the most recent Stripe Subscription for a user</p>
</li>
<li><p>HasSubscriptionActive - Set to true if user has an active subscription</p>
</li>
<li><p>IsHost - Set to true if the user is a host, false if they’re a regular user.</p>
</li>
<li><p>IsEnabled - Set to true if a user is in good standing and allowed to log in</p>
</li>
</ul>
<p>ASP.NET Core Identity (Identity) has support for using Entity Framework Core (EF Core) for data storage through the IdentityDbContext type. To make the EF Core and Identity aware of the OasisHubUser model, IdentityDbContext needs to be extended as well.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OasisHubsDbContext</span> : <span class="hljs-title">IdentityDbContext</span>&lt;<span class="hljs-title">OasisHubsUser</span>&gt; {
   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">OasisHubsDbContext</span>(<span class="hljs-params">DbContextOptions&lt;OasisHubsDbContext&gt; options</span>)
       : <span class="hljs-title">base</span>(<span class="hljs-params">options</span>)</span> {
   }
}
</code></pre>
<p>Now that this is done, Identity and the customer EF Core class needs to be registered in the service collection for ASP.NET Core.</p>
<pre><code class="lang-csharp">services.AddPooledDbContextFactory&lt;OasisHubsDbContext&gt;(options =&gt;
 options.UseSqlServer(config.GetConnectionString(<span class="hljs-string">"OasisHubsSQLServer"</span>),
    opts =&gt; opts.EnableRetryOnFailure()));

services.AddIdentity&lt;OasisHubsUser, IdentityRole&gt;(options =&gt; {
    options.User.RequireUniqueEmail = <span class="hljs-literal">true</span>;
  })
 .AddDefaultTokenProviders()
 .AddEntityFrameworkStores&lt;OasisHubsDbContext&gt;();
</code></pre>
<p>The code above adds the custom IdentityDbContext and wires up Identity with ASP.NET Core with some custom password settings. While we’re here, why not register the Stripe client as well.</p>
<pre><code class="lang-csharp">StripeConfiguration.ApiKey = config.GetValue&lt;<span class="hljs-keyword">string</span>&gt;(<span class="hljs-string">"SecretKey"</span>);

<span class="hljs-keyword">var</span> appInfo = <span class="hljs-keyword">new</span> AppInfo { Name = <span class="hljs-string">"Oasis Hubs"</span>, Version = <span class="hljs-string">"0.1.0"</span> };
StripeConfiguration.AppInfo = appInfo;

services.AddHttpClient(<span class="hljs-string">"Stripe"</span>);
services.AddTransient&lt;IStripeClient, StripeClient&gt;(s =&gt; {
     <span class="hljs-keyword">var</span> clientFactory = s.GetRequiredService&lt;IHttpClientFactory&gt;();

     <span class="hljs-keyword">var</span> sysHttpClient = <span class="hljs-keyword">new</span> SystemNetHttpClient(
        httpClient: clientFactory.CreateClient(<span class="hljs-string">"Stripe"</span>),
        maxNetworkRetries: StripeConfiguration.MaxNetworkRetries,
        appInfo: appInfo,
        enableTelemetry: StripeConfiguration.EnableTelemetry);

     <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StripeClient(apiKey: StripeConfiguration.ApiKey, httpClient: sysHttpClient);
});
</code></pre>
<blockquote>
<p>The code above isn’t absolutely necessary but it does provide a more elegant way of injecting an instance of StripeClient that works alongside HttpClientFactory.</p>
</blockquote>
<p>With all this in place, we can move on to creating the sign up page. For the sake of brevity, we’ll focus on the Razor Page PageModel and not the html form markup.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675596891/f842efcc-08f0-4f81-aa31-33259692dc85.png" alt="Account Sign up" /></p>
<p>The sign up form is fairly simple; three fields and a button.</p>
<pre><code class="lang-csharp">
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SignUpModel</span> : <span class="hljs-title">PageModel</span> {
   <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> UserManager&lt;OasisHubsUser&gt; _userManager;
   <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> SignInManager&lt;OasisHubsUser&gt; _signInManager;
   <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IStripeClient _stripeClient;
   <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;SignUpModel&gt; _logger;

   [<span class="hljs-meta">BindProperty</span>] [Required] <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

   [<span class="hljs-meta">BindProperty</span>]
   [<span class="hljs-meta">Required</span>]
   [<span class="hljs-meta">EmailAddress</span>]
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Email { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } 

   [<span class="hljs-meta">BindProperty</span>]
   [<span class="hljs-meta">Required</span>]
   [<span class="hljs-meta">DataType(DataType.Password)</span>]
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Password { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

   [<span class="hljs-meta">TempData</span>] <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> ErrorMessage { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">SignUpModel</span>(<span class="hljs-params">UserManager&lt;OasisHubsUser&gt; userManager,
      SignInManager&lt;OasisHubsUser&gt; signInManager,
      IStripeClient stripeClient,
      ILogger&lt;SignUpModel&gt; logger</span>)</span> {
      <span class="hljs-keyword">this</span>._userManager = userManager;
      <span class="hljs-keyword">this</span>._signInManager = signInManager;
      <span class="hljs-keyword">this</span>._stripeClient = stripeClient;
      <span class="hljs-keyword">this</span>._logger = logger;
   }
</code></pre>
<p>The PageModel that serves the sign up page is set up to bind to the html form properties, and it also gets injected with some of the services that we registered earlier like the StripeClient and Identity services.</p>
<pre><code class="lang-csharp">   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">OnPostAsync</span>(<span class="hljs-params"></span>)</span> {
      <span class="hljs-keyword">if</span> (ModelState.IsValid) {
         <span class="hljs-keyword">var</span> customerService = <span class="hljs-keyword">new</span> CustomerService(_stripeClient);
         <span class="hljs-keyword">var</span> customers = <span class="hljs-keyword">await</span> customerService.ListAsync(<span class="hljs-keyword">new</span>() { Email = Email });

         <span class="hljs-keyword">if</span> (customers.Any()) {
            ErrorMessage = <span class="hljs-string">"An account with that email already exists."</span>;
            <span class="hljs-keyword">return</span> Page();
         }

         <span class="hljs-keyword">var</span> options = <span class="hljs-keyword">new</span> CustomerCreateOptions { Name = Name, Email = Email };

         <span class="hljs-keyword">var</span> newCustomer = <span class="hljs-keyword">await</span> customerService.CreateAsync(options);
         <span class="hljs-keyword">var</span> newUser = <span class="hljs-keyword">new</span> OasisHubsUser {
            UserName = Email,
            Email = Email,
            EmailConfirmed = <span class="hljs-literal">true</span>,
            StripeCustomerId = newCustomer.Id
         };

         <span class="hljs-keyword">var</span> createResult = <span class="hljs-keyword">await</span> _userManager.CreateAsync(newUser, Password);
         <span class="hljs-keyword">if</span> (createResult.Succeeded) {
            <span class="hljs-keyword">await</span> _userManager.AddClaimAsync(newUser, <span class="hljs-keyword">new</span> Claim(ClaimsConstants.OASIS_USER_TYPE, <span class="hljs-string">"customer"</span>));
            <span class="hljs-keyword">await</span> _signInManager.SignInAsync(newUser, isPersistent: <span class="hljs-literal">false</span>);
            <span class="hljs-keyword">return</span> RedirectToPage(<span class="hljs-string">"/Index"</span>);
         }

         <span class="hljs-keyword">this</span>._logger.LogWarning(<span class="hljs-string">"Unable to create user"</span>);
         <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> error <span class="hljs-keyword">in</span> createResult.Errors) {
            ModelState.AddModelError(<span class="hljs-keyword">string</span>.Empty, error.Description);
         }
      }

      <span class="hljs-keyword">return</span> Page();
   }
</code></pre>
<p>When the form is submitted and the post handler fires, a few things happen on the backend.</p>
<p>First, the form fields get validated. Once that is successful, an instance of Stripe’s CustomerService is created and is passed the injected StripeClient.</p>
<p>The CustomerService is used to create a customer object in the Oasis Hub Stripe account for the newly registered user. Every Oasis Hub user will have a Stripe customer object created for them with the Oasis Hubs Stripe account regardless if they are a host or not. This is so that we can take account of the participants on both sides of a payment. After the customer object is created, the Id is attached to the OasisHubsUser which is then stored in the Identity database and the user is signed in.</p>
<p>If you’re interested in seeing the sign in/out page implementations, take a look at the linked Github repository below.</p>
<h2 id="heading-host-onboarding">Host Onboarding</h2>
<p>At this point, users are able to register and sign in to Oasis Hubs, but they still need to be onboarded before they can become hosts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675598253/7dde15a3-bf98-48ef-950e-25a858a0ea21.png" alt="Host sign up page" /></p>
<p>Once an authenticated user navigates to the host sign up page and clicks the “Become a Host” button, the OnPostAsync handler for the HostSignUpModel PageModel model gets invoked. There’s a few things going on here so let’s break it up.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> acOptions = <span class="hljs-keyword">new</span> AccountCreateOptions {
   Country = <span class="hljs-string">"US"</span>,
   Type = <span class="hljs-string">"express"</span>,
   Email = currentUser.Email,
   Company =
     <span class="hljs-keyword">new</span> AccountCompanyOptions {
        Name = companyName,
        Structure =
          <span class="hljs-string">"single_member_llc"</span>
     },
   Capabilities =
     <span class="hljs-keyword">new</span> AccountCapabilitiesOptions {
        UsBankAccountAchPayments =
          <span class="hljs-keyword">new</span> AccountCapabilitiesUsBankAccountAchPaymentsOptions { Requested =
                                                                     <span class="hljs-literal">true</span> },
        LinkPayments =
          <span class="hljs-keyword">new</span> AccountCapabilitiesLinkPaymentsOptions { Requested = <span class="hljs-literal">true</span> },
        CardPayments =
          <span class="hljs-keyword">new</span> AccountCapabilitiesCardPaymentsOptions { Requested = <span class="hljs-literal">true</span> },
        Transfers = <span class="hljs-keyword">new</span> AccountCapabilitiesTransfersOptions { Requested = <span class="hljs-literal">true</span> }
     },
   BusinessType = <span class="hljs-string">"company"</span>,
   BusinessProfile =
     <span class="hljs-keyword">new</span> AccountBusinessProfileOptions {
        Name = companyName,
        Mcc = <span class="hljs-string">"6513"</span>, <span class="hljs-comment">// https://stripe.com/docs/connect/setting-mcc#list</span>
        ProductDescription = <span class="hljs-string">"Remote work rental space"</span>,
        SupportEmail = currentUser.Email
     },
   TosAcceptance =
     <span class="hljs-keyword">new</span> AccountTosAcceptanceOptions { ServiceAgreement = <span class="hljs-string">"full"</span> },
   Metadata = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>&gt; { [<span class="hljs-string">"owner.customer.id"</span>] =
                                                 currentUser.StripeCustomerId }
};
<span class="hljs-keyword">var</span> accountService = <span class="hljs-keyword">new</span> AccountService(<span class="hljs-keyword">this</span>._stripeClient);
<span class="hljs-keyword">var</span> newExpressAccount = <span class="hljs-keyword">await</span> accountService.CreateAsync(acOptions);
</code></pre>
<p>First, the user record is retrieved and then an Express account is created using the Stripe AccountService class. The express account represents the host’s business that they wish to connect to the primary Oasis Hubs business. In Stripe terminology, we call the primary business the “platform" account and the host account would be known as a “connected account”.</p>
<p>The majority of the code here is prefilling some account information to help speed up the onboarding process for the user. For example, it sets the account type, country, business type, support email, and some metadata. It even requests that the account have certain capabilities such as the ability to take payments from US bank accounts, credit cards, or <a target="_blank" href="https://stripe.com/payments/link">Link</a>.</p>
<blockquote>
<p>As we request more capabilities, Stripe will ensure that the hosts provide all of the KYC information required to enable each one.</p>
</blockquote>
<pre><code class="lang-csharp"><span class="hljs-comment">// update Stripe customer with express account Id</span>
<span class="hljs-keyword">var</span> cuOptions = <span class="hljs-keyword">new</span> CustomerUpdateOptions {
   Metadata = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>&gt; { [<span class="hljs-string">"host.account.id"</span>] =
                                                 newExpressAccount.Id }
};
<span class="hljs-keyword">var</span> customerService = <span class="hljs-keyword">new</span> CustomerService(<span class="hljs-keyword">this</span>._stripeClient);
<span class="hljs-keyword">await</span> customerService.UpdateAsync(currentUser.StripeCustomerId, cuOptions);

<span class="hljs-comment">// Link account to platform</span>
<span class="hljs-keyword">var</span> basePageUri = _linkGenerator.GetUriByPage(<span class="hljs-keyword">this</span>.HttpContext, <span class="hljs-string">"/Index"</span>);
<span class="hljs-keyword">var</span> alcOptions =
  <span class="hljs-keyword">new</span> AccountLinkCreateOptions { Account = newExpressAccount.Id,
                                 RefreshUrl = <span class="hljs-string">$"<span class="hljs-subst">{basePageUri}</span>/hosts/refresh"</span>,
                                 ReturnUrl = <span class="hljs-string">$"<span class="hljs-subst">{basePageUri}</span>/hosts/complete"</span>,
                                 Type = <span class="hljs-string">"account_onboarding"</span>,
                                 Collect = <span class="hljs-string">"eventually_due"</span> };

<span class="hljs-keyword">var</span> accountLinkService = <span class="hljs-keyword">new</span> AccountLinkService(_stripeClient);
<span class="hljs-keyword">var</span> acLink = <span class="hljs-keyword">await</span> accountLinkService.CreateAsync(alcOptions);
<span class="hljs-keyword">return</span> Redirect(acLink.Url);
</code></pre>
<p>All that’s left now is to redirect the user to Stripe’s hosted onboarding for the Express account that was just created. To do that, we have to make use of another service. Using the AccountLinkService, we provide the Id for the Express account, set the Type to “account_onboarding”, and also set Collect to “eventually_due” to collect as much company information as necessary during onboarding. Also notice that the RefreshUrl and ReturnUrl properties are set all well. These create links back to the Oasis Hubs site to either refresh the onboarding session or redirect the user once onboarding is successfully completed.</p>
<p>The create account link will contain a URL property that can be used to redirect the user to the hosted <a target="_blank" href="https://stripe.com/docs/connect/required-verification-information">onboarding experience</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709675599841/0f166603-2324-44e9-81b0-db7da66d0c6c.png" alt="Stripe hosted onboarding" /></p>
<p>After the user completes the onboarding process, they should be redirected to the Oasis Hubs main site. In the background, Stripe will process and verify the collected information. Throughout the onboarding and verification process, Stripe will fire webhook events associated with the Oasis Hubs account that should be monitored to check the current status. For onboarding, the account.updated event will tell us if and when the business was verified and can start accepting pay outs. Now we can move forward to handle other concerns like invoicing, handling webhook events and testing.</p>
<h2 id="heading-summary">Summary</h2>
<p>Hopefully at this point, you have a better understanding of the considerations that need to be made when integrating Stripe Connect into an ASP.NET Core application. We've covered everything from custom Identity models, creating various stripe objects, and working with Express connect accounts.</p>
<p>In the next post in this series, we will discuss handling webhooks for connected accounts and take a look at some key events that we should pay attention to.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/stripe-samples/oasis-hubs-dotnet">Oasis Hubs GitHub Repository</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/connect">Stripe Connect</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/connect/explore-connect-guide">Stripe Connect guide</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/connect/accounts">Choose your Connect account type</a></p>
</li>
<li><p><a target="_blank" href="https://support.stripe.com/questions/know-your-customer-obligations">Know Your Customer (KYC)</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Inspecting application metrics with dotnet-monitor]]></title><description><![CDATA[dotnet-monitor is a command line tool that makes it easier to get access to diagnostics information in a dotnet process.
In the episode, Rich is joined by Sourabh who explains to us the importance of gathering application diagnostics and also gives u...]]></description><link>https://cecilphillip.dev/inspecting-application-metrics-with-dotnet-monitor</link><guid isPermaLink="true">https://cecilphillip.dev/inspecting-application-metrics-with-dotnet-monitor</guid><category><![CDATA[dotnet]]></category><category><![CDATA[performance]]></category><category><![CDATA[monitoring]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Tue, 12 Jan 2021 16:54:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610470435608/d_Tqe3OGI.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>dotnet-monitor is a command line tool that makes it easier to get access to diagnostics information in a dotnet process.</p>
<p>In the episode, Rich is joined by Sourabh who explains to us the importance of gathering application diagnostics and also gives us a demo of how to run dotnet-monitor in Kubernetes.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=hbgPvjTJSLY">https://www.youtube.com/watch?v=hbgPvjTJSLY</a></div>
<ul>
<li><a href="https://aka.ms/dotnet-monitor" target="_blank">Introducing dotnet-monitor</a></li>
<li><a href="https://github.com/dotnet/diagnostics/" target="_blank">.NET Diagnostics on GitHub</a></li>
<li><a href="https://docs.microsoft.com/dotnet/core/diagnostics/diagnostics-in-containers?WT.mc_id=dotnet-00000-cephilli" target="_blank">Collect diagnostics in containers</a></li>
<li><a href="https://docs.microsoft.com/dotnet/core/diagnostics/?WT.mc_id=dotnet-00000-cephilli" target="_blank">What diagnostic tools are available in .NET Core?</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[On.NET Episode: Paying it forward with mentorship]]></title><description><![CDATA[While we were at the Caribbean Developers Conference, we caught up with Noe Branagan to discuss the work he does with teaching and mentoring students in robotics!
https://www.youtube.com/watch?v=axDTYxga75Q
If you liked this video and would like to s...]]></description><link>https://cecilphillip.dev/onnet-episode-paying-it-forward-with-mentorship</link><guid isPermaLink="true">https://cecilphillip.dev/onnet-episode-paying-it-forward-with-mentorship</guid><category><![CDATA[dotnet]]></category><category><![CDATA[mentor]]></category><category><![CDATA[robotics]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Thu, 26 Mar 2020 17:21:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1585243230586/HImXgK3RK.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While we were at the Caribbean Developers Conference, we caught up with Noe Branagan to discuss the work he does with teaching and mentoring students in robotics!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=axDTYxga75Q" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=axDTYxga75Q</a></div>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
]]></content:encoded></item><item><title><![CDATA[On.NET Episode: Improving accessibility of health insurance in the Dominican Republic]]></title><description><![CDATA[It can be very difficult to get health insurance for many people across the globe. Contigo helps lower the cost of insurance and provide information about the options that are available to their community.
In this episode, Ismael talks to us about th...]]></description><link>https://cecilphillip.dev/onnet-episode-improving-accessibility-of-health-insurance-in-the-dominican-republic</link><guid isPermaLink="true">https://cecilphillip.dev/onnet-episode-improving-accessibility-of-health-insurance-in-the-dominican-republic</guid><category><![CDATA[dotnet]]></category><category><![CDATA[community]]></category><category><![CDATA[Azure]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Tue, 17 Mar 2020 14:02:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1584453629008/PsezldWjS.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It can be very difficult to get health insurance for many people across the globe. Contigo helps lower the cost of insurance and provide information about the options that are available to their community.</p>
<p>In this episode, Ismael talks to us about the issues around getting proper insurance coverage in the Dominican Republic. He also shares with us how .NET and Azure are enabling them to provide the solutions around this effort.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=_fD2xNu-LQ4" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=_fD2xNu-LQ4</a></div>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
]]></content:encoded></item><item><title><![CDATA[On.NET Episode: Building up open source developer communities in the Dominican Republic]]></title><description><![CDATA[User groups are an essential part of software development culture. Starting a user group and building a community is not an easy task. Keeping your group running can be even harder.
In this episode, Scott chats with Andres Pineda about getting starte...]]></description><link>https://cecilphillip.dev/onnet-episode-building-up-open-source-developer-communities-in-the-dominican-republic</link><guid isPermaLink="true">https://cecilphillip.dev/onnet-episode-building-up-open-source-developer-communities-in-the-dominican-republic</guid><category><![CDATA[dotnet]]></category><category><![CDATA[community]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Tue, 10 Mar 2020 14:18:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1583849818950/UN3S4pxTP.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>User groups are an essential part of software development culture. Starting a user group and building a community is not an easy task. Keeping your group running can be even harder.</p>
<p>In this episode, Scott chats with Andres Pineda about getting started with open source and how he helped kickstart the .NET user groups in the Dominican Republic.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=5U2s47IAQlQ" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=5U2s47IAQlQ</a></div>
<p> </p>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><a target='_blank' rel='noopener noreferrer'  href="https://dotnetfoundation.org/get-involved">.NET Meetups and User groups</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://getinvolved.hanselman.com/">Become a social Developer</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://cdc.dev/">Caribbean Developers Conference</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[On .NET Episode: Empowering digital transformation with .NET, ML and Azure]]></title><description><![CDATA[Data entry automation is a huge space and has tremendous impact to many countries around the world. Raul Roa is a software engineer in the Dominican Republic who is using .NET Core and Azure to help drive the digital transformation for his country an...]]></description><link>https://cecilphillip.dev/on-net-episode-empowering-digital-transformation-with-net-ml-and-azure</link><guid isPermaLink="true">https://cecilphillip.dev/on-net-episode-empowering-digital-transformation-with-net-ml-and-azure</guid><category><![CDATA[Azure]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Cloud Computing]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Thu, 05 Mar 2020 16:27:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1583425664165/wyueS0NyX.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Data entry automation is a huge space and has tremendous impact to many countries around the world. Raul Roa is a software engineer in the Dominican Republic who is using .NET Core and Azure to help drive the digital transformation for his country and many others around the world.</p>
<p>In this episode, we chat with Raul at the Caribbean Developers Conference to learn how he&#39;s using technology to improve the lives of the people in this country.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=SriyUJEpMAU" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=SriyUJEpMAU</a></div>
<p> </p>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><a target='_blank' rel='noopener noreferrer'  href="https://dotnet.microsoft.com/apps/machinelearning-ai/ml-dotnet?WT.mc_id=ondotnet-devto-cephilli">Getting started with ML .NET</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://dotnet.microsoft.com/learn?WT.mc_id=ondotnet-c9-cephilli">Get started with .NET</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/machine-learning/overview-what-is-azure-ml?WT.mc_id=ondotnet-devto-cephilli">Machine Learning in Azure</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[On.NET Episode: Lesser known features of the Cosmos DB SDK for .NET]]></title><description><![CDATA[In this episode, Jeremy chats with software engineer Matías Quaranta about some of the lesser known features of the Cosmos DB SDK for .NET. Matias discusses some useful patterns for managing the lifetime of the client, implementing custom serializers...]]></description><link>https://cecilphillip.dev/onnet-episode-lesser-known-features-of-the-cosmos-db-sdk-for-net</link><guid isPermaLink="true">https://cecilphillip.dev/onnet-episode-lesser-known-features-of-the-cosmos-db-sdk-for-net</guid><category><![CDATA[Azure]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Wed, 12 Feb 2020 17:07:10 GMT</pubDate><content:encoded><![CDATA[<p>In this episode, <a target='_blank' rel='noopener noreferrer'  href="https://twitter.com/jeremylikness">Jeremy</a> chats with software engineer <a target='_blank' rel='noopener noreferrer'  href="https://twitter.com/ealsur">Matías Quaranta</a> about some of the lesser known features of the Cosmos DB SDK for .NET. Matias discusses some useful patterns for managing the lifetime of the client, implementing custom serializers and some other interesting features.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=uFWWkzYL7tA" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=uFWWkzYL7tA</a></div>
<p> </p>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/ealsur/ondotnet-cosmosdb">GitHub repo with the sample code</a></p>
</li>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/cosmos-db/performance-tips?WT.mc_id=ondotnet-devto-cephilli">Performance tips for Azure Cosmos DB and .NET</a></p>
</li>
</ul>
<ul>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/learn/paths/work-with-nosql-data-in-azure-cosmos-db?WT.mc_id=ondotnet-devto-cephilli">Work with NoSQL data in Azure Cosmos DB</a></p>
</li>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/cosmos-db/sql-api-sdk-dotnet-standard?WT.mc_id=ondotnet-devto-cephilli">Cosmos DB .NET Standard SDK release notes</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[On.NET Episode: Messaging with Azure Event Hubs]]></title><description><![CDATA[In this episode, Serkant Karaca and Shubha Vijayasarathy from the Azure Event Hubs team join us to talk about how and when to use Azure Event Hubs as the messaging component in our .NET applications. They'll discuss use cases, cover topics like parti...]]></description><link>https://cecilphillip.dev/onnet-episode-messaging-with-azure-event-hubs</link><guid isPermaLink="true">https://cecilphillip.dev/onnet-episode-messaging-with-azure-event-hubs</guid><category><![CDATA[dotnet]]></category><category><![CDATA[Azure]]></category><category><![CDATA[messaging]]></category><category><![CDATA[big data]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Tue, 21 Jan 2020 15:07:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1579619182325/_iKZuBJ0z.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this episode, <a target='_blank' rel='noopener noreferrer'  href="https://twitter.com/KaracaSerkant">Serkant Karaca</a> and <a target='_blank' rel='noopener noreferrer'  href="https://twitter.com/ShubhaVijaya">Shubha Vijayasarathy</a> from the Azure Event Hubs team join us to talk about how and when to use Azure Event Hubs as the messaging component in our .NET applications. They&#39;ll discuss use cases, cover topics like partitioning and also show how to use the .NET SDK for Event Hubs.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=DDDjFQSQyF4" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=DDDjFQSQyF4</a></div>
<p> </p>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/event-hubs?WT.mc_id=ondotnet-hashnode-cephilli">Azure Event Hubs Documentation</a></p>
</li>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://github.com/Azure/azure-event-hubs/tree/master/samples/DotNet/Microsoft.Azure.EventHubs?WT.mc_id=ondotnet-hashnode-cephilli">GitHub repo with samples</a></p>
</li>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/learn/modules/enable-reliable-messaging-for-big-data-apps-using-event-hubs?WT.mc_id=ondotnet-hashnode-cephilli">Enable reliable messaging for Big Data applications using Azure Event Hubs</a></p>
</li>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/event-grid/compare-messaging-services?WT.mc_id=ondotnet-hashnode-cephilli">Choose between Azure messaging services</a></p>
</li>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/iot-hub?WT.mc_id=ondotnet-hashnode-cephilli">Azure IoT Documentation</a></p>
</li>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-dotnet-standard-getstarted-send?WT.mc_id=ondotnet-hashnode-cephilli">Send events to or receive events from Azure Event Hubs using .NET Core</a></p>
</li>
<li><p><a target='_blank' rel='noopener noreferrer'  href="https://www.nuget.org/packages/Microsoft.Azure.EventHubs?WT.mc_id=ondotnet-hashnode-cephilli">Microsoft.Azure.EventHubs NuGet package</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[On.NET Episode: Azure Service Bus Core Features]]></title><description><![CDATA[In this episode, Ashish Chhabria returns to talk to us about some of the core features and capabilities  of Azure Service Bus. We will see demos of how to leverage things like Peek Lock, Receive Delete, Rules and Transactions.
https://www.youtube.com...]]></description><link>https://cecilphillip.dev/onnet-episode-azure-service-bus-core-features</link><guid isPermaLink="true">https://cecilphillip.dev/onnet-episode-azure-service-bus-core-features</guid><category><![CDATA[dotnet]]></category><category><![CDATA[Azure]]></category><category><![CDATA[messaging]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Wed, 08 Jan 2020 16:44:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1578502256051/veFlyqTJH.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this episode, <a target='_blank' rel='noopener noreferrer'  href="https://twitter.com/ashishc1">Ashish Chhabria</a> returns to talk to us about some of the core features and capabilities  of Azure Service Bus. We will see demos of how to leverage things like Peek Lock, Receive Delete, Rules and Transactions.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=cdSSA5L-Ndw" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=cdSSA5L-Ndw</a></div>
<p> </p>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview?WT.mc_id=ondotnet-devto-cephilli">What is Azure Service Bus</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/event-grid/compare-messaging-services?WT.mc_id=ondotnet-devto-cephilli">Choose between Azure messaging services</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/architecture/patterns/category/messaging?WT.mc_id=ondotnet-devto-cephilli">Messaging Patterns</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://github.com/Azure/azure-service-bus">Azure Service Bus Samples</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[On.NET Episode: Azure Service Bus from the ground up]]></title><description><![CDATA[In this episode, we're joined by Ashish Chhabria who is a Program Manager on the Azure Service Bus team. He's here to talk to us about where Service Bus fits in the Azure messaging landscape and also explain some of its target use cases.
https://www....]]></description><link>https://cecilphillip.dev/onnet-episode-azure-service-bus-from-the-ground-up</link><guid isPermaLink="true">https://cecilphillip.dev/onnet-episode-azure-service-bus-from-the-ground-up</guid><category><![CDATA[dotnet]]></category><category><![CDATA[Azure]]></category><category><![CDATA[messaging]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Sun, 05 Jan 2020 20:29:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1578502039387/9epHcTezY.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this episode, we&#39;re joined by <a target='_blank' rel='noopener noreferrer'  href="https://twitter.com/ashishc1">Ashish Chhabria</a> who is a Program Manager on the Azure Service Bus team. He&#39;s here to talk to us about where Service Bus fits in the Azure messaging landscape and also explain some of its target use cases.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=FRzMPqViwuY" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=FRzMPqViwuY</a></div>
<p> </p>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview?WT.mc_id=ondotnet-hashnode-cephilli">What is Azure Service Bus</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/event-grid/compare-messaging-services?WT.mc_id=ondotnet-hashnode-cephilli">Choose between Azure messaging services</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/architecture/patterns/category/messaging?WT.mc_id=ondotnet-hashnode-cephilli">Messaging Patterns</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Learning about Asynchronous Streams and LINQ in C# 8]]></title><description><![CDATA[One of the features from C# 8 that I'm really excited about is Asynchronous Streams. Essentially, it enables developers to both create and consume a constant stream of data in an asynchronous way.
This feature enables some interesting possibilities f...]]></description><link>https://cecilphillip.dev/learning-about-asynchronous-streams-and-linq-in-c-8</link><guid isPermaLink="true">https://cecilphillip.dev/learning-about-asynchronous-streams-and-linq-in-c-8</guid><category><![CDATA[dotnet]]></category><category><![CDATA[C#]]></category><category><![CDATA[dotnetcore]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Mon, 30 Sep 2019 15:30:21 GMT</pubDate><content:encoded><![CDATA[<p>One of the features from <a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#asynchronous-streams?WT.mc_id=dotnet-devto-cephilli">C# 8</a> that I&#39;m really excited about is Asynchronous Streams. Essentially, it enables developers to both create and consume a constant stream of data in an asynchronous way.</p>
<p>This feature enables some interesting possibilities for .NET developers particularly in the realm of IoT, Big Data processing, and machine learning.</p>
<p>If you&#39;re interested reading some more about this feature, my colleague Anthony Chu has an interesting article here on DEV that I&#39;d recommend taking a look at.
<a target='_blank' rel='noopener noreferrer'  href="https://dev.to/dotnet/what-s-the-big-deal-with-iasyncenumerable-t-in-net-core-3-1eii">What&#39;s the big deal with IAsyncEnumerable&lt;T&gt; in .NET Core 3.0?</a></p>
<p>If you&#39;re the type of person that learns better by doing, take a look at this tutorial that will guide through the various language constructs involved in creating and consuming an asynchronous stream.</p>
<p><strong>Tutorial</strong>
<a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/generate-consume-asynchronous-stream?WT.mc_id=dotnet-devto-cephilli">Generate and consume async streams using C# 8.0 and .NET Core 3.0</a></p>
<p>One of the interesting things about the asynchronous streams feature it that it leverages familiar language constructs for working with enumerables and enumerators in C#. One thing that&#39;s missing out of the box, was the ability for developers to apply their knowledge of <a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq?WT.mc_id=dotnet-devto-cephilli">LINQ</a> to process these data streams.</p>
<p>Well, it seems that the open source community has taken on the challenge and is working on <a target='_blank' rel='noopener noreferrer'  href="https://www.nuget.org/packages/System.Linq.Async?WT.mc_id=dotnet-devto-cephilli">System.Linq.Async</a>. This NuGet package extends the IAsyncEnumerable interface to add many common LINQ operators and more.</p>
<p>In this episode, Bart De Smet comes on to talk about the System.Linq.Async nuget package that adds some common Linq query operators to IAsyncEnumerable. </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=Ktl8K2b1-WU" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=Ktl8K2b1-WU</a></div>
<p> </p>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw"> .NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><a target='_blank' rel='noopener noreferrer'  href="https://github.com/dotnet/roslyn/blob/master/docs/features/async-streams.md?WT.mc_id=dotnet-devto-cephilli">Asynchronous Streams feature document</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://github.com/dotnet/reactive?WT.mc_id=dotnet-devto-cephilli">Reactive Extensions and System.Linq.Async on GitHub</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://www.nuget.org/packages/System.Linq.Async?WT.mc_id=dotnet-devto-cephilli">System.Linq.Async on NuGet</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Creating custom bindings for Azure Functions]]></title><description><![CDATA[In my opinion, one of the most extraordinary features of Azure Functions is its ability to declaratively connect to an external resource, and consume it within your function code without the needing to know very much about the underlying API. 
This c...]]></description><link>https://cecilphillip.dev/creating-custom-bindings-for-azure-functions</link><guid isPermaLink="true">https://cecilphillip.dev/creating-custom-bindings-for-azure-functions</guid><category><![CDATA[Azure]]></category><category><![CDATA[serverless]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Wed, 11 Sep 2019 14:22:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1568211653214/xyESsIQgd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my opinion, one of the most extraordinary features of <a target='_blank' rel='noopener noreferrer'  href="https://azure.microsoft.com/en-us/services/functions/?WT.mc_id=devto-dotnet-cephilli">Azure Functions</a> is its ability to declaratively connect to an external resource, and consume it within your function code without the needing to know very much about the underlying API. </p>
<p>This concept is known as <em>bindings</em>, and with it you can interact with various external services as inputs or outputs to your serveless function. Of course, bindings are optional, and a function might have one or multiple input and/or output bindings.</p>
<p>Out of the box, Azure Functions provides a variety of <a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?WT.mc_id=devto-dotnet-cephilli#supported-bindings">supported bindings</a>. These allow you to easily integrate with services like <a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-twilio?WT.mc_id=devto-dotnet-cephilli">Twilio</a>, <a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus?WT.mc_id=devto-dotnet-cephilli">Azure Service Bus</a>, <a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-sendgrid?WT.mc_id=devto-dotnet-cephilli">SendGrid</a>, <a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-cosmosdb?WT.mc_id=devto-dotnet-cephilli">Cosmos DB</a>, or even <a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook?WT.mc_id=devto-dotnet-cephilli">HTTP</a>.</p>
<p>However, what if you want to create a binding to a service that&#39;s not in the list of supported bindings? This might be a 3rd party service that your company relies on or maybe even an internal service you created. </p>
<p>This is was such a common question that we decided to ask our friend <a target='_blank' rel='noopener noreferrer'  href="https://twitter.com/Ealsur">Matías Quaranta</a> to join us on <em>On .NET</em> to show us exactly how we can create our own custom bindings for Azure Functions. Matias was also kind enough to provide a <a target='_blank' rel='noopener noreferrer'  href="https://github.com/ealsur/functions-extension-101">GitHub repository</a> with step-by-step instructions that any of us may follow along with.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=vKrUn9qiUI8&t=50" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=vKrUn9qiUI8&t=50</a></div>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?WT.mc_id=devto-dotnet-cephilli">Azure Functions triggers and bindings concepts</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://github.com/ealsur/functions-extension-101">Walkthrough and code samples</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Leveraging the Dependency Injection support in Azure Functions]]></title><description><![CDATA[For many developers, techniques such as dependency injection and inversion of control have become commonplace in their codebases. Particularly in the .NET space, there are tons of open source frameworks and libraries that make incorporating these pra...]]></description><link>https://cecilphillip.dev/leveraging-the-dependency-injection-support-in-azure-functions</link><guid isPermaLink="true">https://cecilphillip.dev/leveraging-the-dependency-injection-support-in-azure-functions</guid><category><![CDATA[Azure]]></category><category><![CDATA[serverless]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Wed, 11 Sep 2019 14:18:35 GMT</pubDate><content:encoded><![CDATA[<p>For many developers, techniques such as <a target='_blank' rel='noopener noreferrer'  href="https://en.wikipedia.org/wiki/Dependency_injection">dependency injection</a> and <a target='_blank' rel='noopener noreferrer'  href="https://en.wikipedia.org/wiki/Inversion_of_control">inversion of control</a> have become commonplace in their codebases. Particularly in the .NET space, there are tons of open source frameworks and libraries that make incorporating these practices much easier.</p>
<p>As the software industry continues to steadily adopt more serverless technologies, development teams continue to search for ways to leverage their existing investments into this new serverless world. They want to be able to create modular components and dynamically swap out their implementations without having to rewrite or deploy their code. They want to be able to write focused unit tests for their code and create mock implementations that abstract away external dependencies. </p>
<p>These are just some of the reasons why support for dependency injection was added to serverless functions targeting .NET Core in <a target='_blank' rel='noopener noreferrer'  href="https://azure.microsoft.com/en-us/services/functions?WT.mc_id=ondotnet-hashnode-cephilli">Azure Functions</a> v2.</p>
<p>We were so excited about this feature, we decided to invite <a target='_blank' rel='noopener noreferrer'  href="https://twitter.com/codesapien">Fabio Cavalcante</a> from the Azure Functions team to come on the <a target='_blank' rel='noopener noreferrer'  href="https://channel9.msdn.com/Shows/On-NET?WT.mc_id=ondotnet-hashnode-cephilli">On .NET</a> show to give us a demo of how it all works.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://www.youtube.com/watch?v=LTPbaNzJd18&time_continue=45" data-card-controls="0" data-card-theme="light">https://www.youtube.com/watch?v=LTPbaNzJd18&time_continue=45</a></div>
<p>If you liked this video and would like to see some more of our .NET content, please subscribe to our <a target='_blank' rel='noopener noreferrer'  href="https://www.youtube.com/channel/UCvtT19MZW8dq5Wwfu6B0oxw">.NET Developers YouTube Channel</a>.</p>
<h2 id="useful-links">Useful Links</h2>
<ul>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection?WT.mc_id=ondotnet-hashnode-cephilli">Use dependency injection in .NET Azure Functions</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://github.com/fabiocav/azfunc-di-demo">Code demos from the show</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://azure.microsoft.com/en-us/blog/advancing-the-developer-experience-for-serverless-apps-with-azure-functions/?WT.mc_id=ondotnet-hashnode-cephilli">Advancing the developer experience for serverless apps with Azure Functions</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-vs-code?WT.mc_id=ondotnet-hashnode-cephilli">Create your first function using Visual Studio Code</a></li>
<li><a target='_blank' rel='noopener noreferrer'  href="https://dotnet.microsoft.com/">Get started with .NET</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Sending custom events to Azure Event Grid]]></title><description><![CDATA[Azure Event Grid is a new managed service that was recently released for public review, so I thought I'd take some time out to give it try. It's essentially a cloud scale event routing service, which comes in pretty handy if you're building reactive ...]]></description><link>https://cecilphillip.dev/sending-custom-events-to-azure-event-grid</link><guid isPermaLink="true">https://cecilphillip.dev/sending-custom-events-to-azure-event-grid</guid><category><![CDATA[Azure]]></category><category><![CDATA[Azure Functions]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Tue, 05 Sep 2017 20:25:41 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1490410006060-e1dc82ab0a70?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=be3777f592713b4a202957d8d490665a" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://images.unsplash.com/photo-1490410006060-e1dc82ab0a70?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=be3777f592713b4a202957d8d490665a" alt="Sending custom events to Azure Event Grid" /></p>
<p>Azure Event Grid is a new managed service that was recently released for public review, so I thought I'd take some time out to give it try. It's essentially a cloud scale event routing service, which comes in pretty handy if you're building <a target="_blank" href="http://www.reactivemanifesto.org/?ref=cecilphillip.com">reactive applications</a>. Publishers will be generating events on one side, handlers responding to events on the other side, and Event Grid in the middle making sure that your events are reliably delivered.</p>
<p>What's exciting about this new service, and probably where you'll get the most value, is how deeply it's integrated into the DNA of the Azure platform. Imagine being able to react to events that get fired when something changes in any of your Azure resources. You can opt in to be notified when a file gets added to Blob storage or a new user gets added to Azure AD. Then, you can have these events trigger an Azure Function App or kick off a serverless workflow with Azure Logic Apps. The combinations you can put together are virtually endless.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341107960/818e8d68-a3d9-4930-8bdf-5fb02df6761e.png" alt="Sending custom events to Azure Event Grid" /></p>
<p>In this post, we're going to see what it takes to publish your own custom events to Event Grid. This will give us a good understanding of what's going on and how to incorporate this service into your code.</p>
<h3 id="heading-creating-an-event-grid-topic">Creating an Event Grid Topic</h3>
<p>The first thing that we'll do is create an Event Grid topic. Topics are where publishers send outgoing events to and where subscribers listen for incoming events.</p>
<p>To create a topic, you'll need the topic name, location and the resource group. In the Azure portal, you can search for and create an <code>Event Grid Topic</code>. If you have the <a target="_blank" href="https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest%3Futm_source%3Dcephilli&amp;utm_medium=blog&amp;ref=cecilphillip.com">Azure CLI</a> installed, you can quickly create a topic on the command line.</p>
<p>    az eventgrid topic create --name  -l westcentralus -g </p>
<p>While the Event Grid is in preview, you'll have to create your topic in westus2 or westcentralus locations.</p>
<h3 id="heading-defining-custom-events">Defining custom events</h3>
<p>Whether you're working with custom or built-in events, there are some manditory fields that you'll expect see in every event.</p>
<ul>
<li><strong>Id</strong> - The unique ID for event.</li>
<li><strong>Subject</strong> - Any name or label that would have meaning for your application. This would be a good place to put the name of the service/entity that the event is related to.</li>
<li><strong>EventType</strong> - A value you can create filters against, e.g. CustomerCreated, BlobDeleted, HttpRequestReceived, etc.</li>
<li><strong>EventTime</strong> - The timestamp for when the event happened in the publisher.</li>
<li><strong>Data</strong> - Relevant information that's need to act on this event. This field is optional.</li>
</ul>
<p>With topics, there is an implicit expectation from the subscriber that certain event types published to a given topic will adhere to a given schema. Think of the event type as contract between publisher and subscriber. If a Blob storage event fires, you might expect that there's some contextual information about the file that was altered.</p>
<p>Here's a simple class in C# that we'll use to model our custom events.</p>
<p>    public class GridEvent where T : class
    {
        public string Id { get; set; }
        public string Subject { get; set; }
        public string EventType { get; set; }
        public T Data { get; set; }
        public DateTime EventTime { get; set; }
    }</p>
<h3 id="heading-handling-events-with-azure-functions">Handling events with Azure Functions</h3>
<p>The next thing we'll need to do is create an endpoint that will receive and process our custom event. Today, the Event Grid preview supports webhook endpoints.</p>
<p>Instead of creating a new Web app to capture the webhook requests, we'll just use <a target="_blank" href="https://docs.microsoft.com/azure/azure-functions?utm_source=cephilli&amp;utm_medium=blog">Azure Functions</a>. We'll create a function app with two functions in it. One will be attached to our filtered Event Grid subscription and the other to the unfiltered subscription.</p>
<p>To keep it simple, we'll use the same code for both functions. The following code is what I wrote for the demo.</p>
<p>    using System.Net;</p>
<p>    public static async Task Run(HttpRequestMessage req, TraceWriter log)
    {
        var data = await req.Content.ReadAsAsync&gt;();
        foreach(var item in data) {
            log.Info($"Event =&gt; {item.EventType} Subject =&gt; {item.Subject}\n");
        }
        log.Info("-------Event data reviewed-------\n");</p>
<p>        return req.CreateResponse(HttpStatusCode.OK);
    }</p>
<p>These functions were created using the <code>HttpTrigger</code> template. That will provide us with unique URL for each function. Keep note of those URLs since we'll need them when we create subscriptions.</p>
<h3 id="heading-creating-subscriptions">Creating subscriptions</h3>
<p>Subscriptions are what tell Event Grid what events on a topic that a subscriber would be interested in. When creating a subscription, you will need to provide an endpoint (the destination) to send the events to, and an optional set of filters.</p>
<p>For this post, we want to create two subscriptions; one with a filter and one without. We'll use the endpoints from the two functions we created above.</p>
<h1 id="heading-no-filters">no filters</h1>
<p>    az eventgrid topic event-subscription create
       --name SubAll 
       --endpoint  
       --resource-group  
       --topic-name </p>
<h1 id="heading-filters">filters</h1>
<p>    az eventgrid topic event-subscription create
       --name SubFiltered 
       --endpoint  
       --resource-group  
       --topic-name 
       --included-event-types filteredEvent</p>
<p>These commands should complete fairly quickly, and now we'll have two subscriptions ready to receive events. Notice that the second call to the Azure CLI uses the <code>--included-event-types</code> flag to provide filters for the Event Grid topic. You can list out multiple event types separate by spaces.</p>
<h3 id="heading-publishing-events">Publishing events</h3>
<p>With all this in place, we should be ready to send some events over to the topic. We will need the endpoint that was created for the topic and also the shared access signature (sas) key.</p>
<p>You can easily copy these from the Azure portal from the blade of the Event Grid topic. They can also be retrieved from the Azure CLI.</p>
<p>This command will return the topic endpoint along with some other basic information.</p>
<p>    az eventgrid topic show --name custom-events -g netcore-eventgrid</p>
<p>This command will return the sas keys.</p>
<p>     az eventgrid topic key list -g netcore-eventgrid -n custom-events</p>
<p>At this point, all that's left to do is to start issuing requests to the topic. The following code sets up an instance of HttpClient, creates the required headers, and sends a few events over to the topic.</p>
<p>    string topicEndpoint = "";
    string sasKey = "";</p>
<p>    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Add("aeg-sas-key", sasKey);
    client.DefaultRequestHeaders.UserAgent.ParseAdd("democlient");</p>
<p>    List&gt; eventList = new List&gt;();
    for (int x = 0; x &lt; 5; x++)
    {
       GridEvent testEvent = new GridEvent
       {
          Subject = $"Event {x}",
          EventType = (x % 2 == 0) ? "allEvents" : "filteredEvent",
          EventTime = DateTime.UtcNow,
          Id = Guid.NewGuid().ToString()
       };
       eventList.Add(testEvent);
    }</p>
<p>    string json = JsonConvert.SerializeObject(eventList);
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, topicEndpoint)
    {
       Content = new StringContent(json, Encoding.UTF8, "application/json")
    };</p>
<p>    HttpResponseMessage response = await client.SendAsync(request);</p>
<p>You might have noticed that there's a simple toggle that alternates the values when the <code>EvenType</code> property is set. If you recall the subscriptions we created earlier, one of them included a filter for the <code>filteredEvent</code> event type. After running the code, we should see the event filtering work for our custom event.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341109202/5716015e-f63d-4b73-bb47-3c11bc545d1e.png" alt="Sending custom events to Azure Event Grid" /></p>
<h3 id="heading-summary">Summary</h3>
<p>Hopefully this post showed you how easy it was to get started with publishing and handling events in Azure Event Grid. Even though it's still in preview, the service looks very promising. I'm looking forward to seeing some of integrations that the product team and as well as the community end up creating.</p>
<p>If you'd like to hear more about the service, I've included some links below that you might find interesting.</p>
<ul>
<li><a target="_blank" href="https://docs.microsoft.com/azure/event-grid?utm_source=cephilli&amp;utm_medium=blog">Azure Event Grid Documentation</a></li>
<li><a target="_blank" href="https://channel9.msdn.com/Shows/Cloud+Cover/Episode-233-Azure-Event-Grid-with-Bahram-Banisadr?ref=cecilphillip.com">Azure Event Grid on the Cloud Cover show</a></li>
<li><a target="_blank" href="https://blog.tomkerkhove.be/2017/08/24/are-service-bus-topics-dead/?ref=cecilphillip.com">Exploring Azure Event Grid - Are Service Bus Topics Dead?</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Setting up Webpack in ASP.NET Web Forms]]></title><description><![CDATA[There are quite a bit of web applications built with and still running on ASP.NET Web Forms. Even though it may not be the new shinny toy to play with, there is no reason why Web Forms projects can't leverage some of the latest tools in the front-end...]]></description><link>https://cecilphillip.dev/setting-up-webpack-in-aspnet-web-forms</link><guid isPermaLink="true">https://cecilphillip.dev/setting-up-webpack-in-aspnet-web-forms</guid><category><![CDATA[ASP.NET]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[webpack]]></category><category><![CDATA[webforms,]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Tue, 18 Jul 2017 03:15:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341117346/5e40890f-8351-4ffb-ab0c-bc0e439d66b4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are quite a bit of web applications built with and still running on ASP.NET Web Forms. Even though it may not be the new shinny toy to play with, there is no reason why Web Forms projects can't leverage some of the latest tools in the front-end development space.</p>
<p>In this post, you'll see how you can integrate <a target="_blank" href="https://webpack.js.org/?ref=cecilphillip.com">webpack</a> into an ASP.NET Web Forms project where we will set it up to transpile and bundle some <code>TypeScript</code> code.</p>
<blockquote>
<p>At the time of writing, the current version of <code>webpack</code> is at 3.2.0, <code>TypeScript</code> is at version 2.4.1, and I'm using <code>NodeJS</code> 7.2.1.</p>
</blockquote>
<p>I'll be using Visual Studio 2017 with the free <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.WebExtensionPack2017&amp;ref=cecilphillip.com">Web Essentials 2017</a> extension installed. If you you're doing any web development in Visual Studio and you haven't checked out Web Essentials yet, go install it now. You'll thank me later. While you're at it, make sure you have a current version of <code>NodeJS</code> installed and available on your path. We're going to start off with a new Web Forms project created using the default template in Visual Studio.</p>
<h4 id="heading-installing-webpack-and-typescript">Installing Webpack and TypeScript</h4>
<p><code>Webpack</code> is a <code>NodeJS</code> package so we can install it using <a target="_blank" href="https://www.npmjs.com/?ref=cecilphillip.com">npm</a>. If you don't feel like opening up the command line, Web Essentials adds a useful feature that makes installing packages fairly easy. If you use the shortcut SHIFT-ALT-0, it will open a package install dialog that can be used to work with front-end package managers. Just select <code>npm</code> from the drop down and enter the packages you want to install, i.e. <code>webpack</code>. While we're here, let's install the <code>TypeScript</code> package and the <code>ts-loader</code> package too.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341114799/bcfa2aa5-9bee-472f-81b3-3268d55dfcb8.png" alt /></p>
<p>Once that's completed, you should notice that a <code>package.json</code> file was added to your project. We'll come back to that later on.</p>
<h4 id="heading-lets-add-some-code">Let's add some code</h4>
<p>There's should be a <code>Scripts</code> folder that got created by default whenever you generated the Web Forms project. Inside that <code>Scripts</code> folder, add two sub folders named <code>src</code> and <code>dist</code>. The <code>src</code> is where the 'raw' <code>TypeScript</code> files are going to go and the transpiled <code>JavaScript</code> will go in <code>dist</code>. If you prefer to keep your <code>TypeScript</code> and <code>JavaScript</code> files together, that's totally fine. I prefer to separate them.</p>
<p>We'll add these two simple scripts below.</p>
<p>    // greeter.ts
    export class Greeter {
        to: string;
        constructor(to: string) {
            this.to = to;
        }
        greet():string {
            return <code>Hi ${this.to}!</code>;
        }
    }</p>
<p>    // main.ts
    import { Greeter } from './greeter';</p>
<p>    let greeter: Greeter = new Greeter('Cecil');
    let greeting: string = greeter.greet();</p>
<p>    console.log(greeting);</p>
<p>At the root of the project, we'll also add a <code>TypeScript</code> configuration file (tsconfig.json) that we'll use to tweak the compiler settings. You can use the <code>Add New Item</code> dialog in Visual Studio to create one for you. Here are the settings I'll be using.</p>
<p>    {
      "compilerOptions": {
        "noImplicitAny": false,
        "noEmitOnError": true,
        "removeComments": false,
        "sourceMap": true,
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "node"<br />      },
      "exclude": [
        "node_modules"<br />      ]
    }</p>
<p><strong>Optional</strong> Visual Studio 2017 comes with some built in support for <code>TypeScript</code>. Since we're going to be using the version of <code>webpack</code> and <code>TypeScript</code> we installed via <code>npm</code>, you might want to turn the <code>TypeScript</code> build feature off. To do that, simply add a <code>TypeScriptCompileBlocked</code> element to a <code>PropertyGroup</code> in your <code>.csproj</code> file and give it a value of true.</p>
    
       true
    


<h4 id="heading-adding-webpack-configuration">Adding Webpack configuration</h4>
<p>By default, <code>webpack</code> will look for a <code>webpack.config.js</code> file in the current directory. Since there's no template for creating the configuration file, we can just add one manually.</p>
<p>    // webpack.config.js
    const path = require("path");</p>
<p>    module.exports = {
        context: path.resolve(<strong>dirname, "./Scripts/src"),
        resolve: {
            extensions: ['.ts']
        },
        entry: {
            main: './main'
        },
        output: {
            publicPath: '/Scripts/dist',
            path: path.resolve(</strong>dirname, './Scripts/dist'),
            filename: '[name].build.js'
        },
        module: {
            rules: [
                {
                    test: /.ts$/,
                    use: 'ts-loader',
                    exclude: /node_modules/
                }
            ]
        }
    };</p>
<p>Let's break down what's going on in this file.</p>
<ul>
<li><strong>context</strong> - This sets the base directory where <code>webpack</code> should look for files. Here we're setting it to the "Script/src" folder we created earlier.</li>
<li><strong>resolve.extensions</strong> - This lists the file extensions <code>webpack</code> should use to resolve modules. In our example, we're only looking for <code>.ts</code> files.</li>
<li><strong>entry</strong> - This specifies the root modules for the application. In our case, <code>main.ts</code> is the root. Since it imports <code>greeter.ts</code> as a dependency, <code>webpack</code> will include both files when generating the bundle.</li>
<li><strong>output</strong> - Here is where we tell <code>webpack</code> to send the final bundles. If you look at the config above, the bundles will be sent to the <code>Scripts/dist</code> folder. The <code>[name]</code> token in the <code>filename</code> setting will be replaced with the name of our entry point, so we should expect a bundle with a name of <code>main.build.js</code>.</li>
<li><strong>module</strong> - Out of the box, <code>webpack</code> only knows how to work with <code>JavaScript</code> files. To add support for bundling <code>TypeScript</code> files, we'll need to add a loader. Earlier on we install the <code>ts-loader</code> via <code>npm</code>. Here we'll create a rule that states any <code>.ts</code> files should be loaded with the <code>ts-loader</code>.</li>
</ul>
<p>The last thing we're going to do to get <code>webpack</code> building the <code>.ts</code> files is to add a simple build script to the <code>package.json</code> file. This file should have been added to the project when we installed the <code>npm</code> packages earlier.</p>
<p>    {
      "devDependencies": {
        "ts-loader": "^2.2.2",
        "typescript": "^2.4.1",
        "webpack": "^3.2.0"
      },
      "scripts": {
        "build": "webpack"
      }
    }</p>
<p>If we were to run <code>npm run build</code> on the command line we should see some output resembling the following screenshot.</p>
<blockquote>
<p>With Web Essentials 2017 installed, you can use the ALT-SPACE shortcut to quickly bring up a command prompt in your project directory.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341115942/85b01c6f-abe7-4d8d-9ca7-ec5712dc9a3f.png" alt /></p>
<p>Take a look inside of the <code>dist</code> folder and you should see the bundled <code>main.build.js</code> file. You will have to manually include this file in your Visual Studio project. In the Solution Explorer, enable <code>Show All Files</code> then you can just right-click the file and select <code>Include in Project</code>.</p>
<h4 id="heading-bundle-on-build">Bundle on build</h4>
<p>Having to open a command prompt and run <code>npm run build</code> gets annoying after a while. What we can do instead is add a <code>MSBuild</code> task to have Visual Studio run our <code>webpack</code> build whenever we hit F5.</p>
      
        
      


<p>With this task added to our <code>.csproj</code> file, we'll have the <code>TypeScript</code> and Web Forms code building together.</p>
<h4 id="heading-registering-with-the-scriptmanager">Registering with the ScriptManager</h4>
<p>The Web Forms project templates come with a <code>BundleConfig.cs</code> file that allows you to add mappings for your client side scripts. You can do things like specify the debug file, the production file, or even a CDN path if you have one.</p>
<p>We can add the <code>webpack</code> bundled file to the ScriptManager like this like this.</p>
<p>    // RegisterBundles method in BundleConfig.cs
    ScriptManager.ScriptResourceMapping.AddDefinition("myscripts", new ScriptResourceDefinition
    {
        Path = "~/Scripts/dist/main.build.min.js",
        DebugPath = "~/Scripts/dist/main.build.js"
    });</p>
    
<p>    
        
            .....
            
            ......
        
    </p>
<p>Wait a minute! We never generated a minified version of our bundle. Let's do that now.</p>
<h4 id="heading-minifying-webpack-bundles">Minifying Webpack bundles</h4>
<p><code>Webpack</code> has built-in support for minification, so we just need to turn it on. The way we do this is by adding a plugin called <code>UglifyJsPlugin</code> to the configuration file. Also, let's set this up so that the plugin is only run during release builds.</p>
<p>Here's what the updated <code>webpack.config.js</code> looks like.</p>
<p>    const path = require("path");
    const webpack = require("webpack");</p>
<p>    module.exports = function (env) {
        const isProduction = env === 'prod';</p>
<p>        return {
            context: path.join(<strong>dirname, "./Scripts/src"),
            resolve: {
                extensions: ['.ts']
            },
            entry: {
                main: './main'
            },
            plugins: isProduction ? [new webpack.optimize.UglifyJsPlugin()]
                                   : [],
            output: {
                publicPath: '/Scripts/dist',
                path: path.join(</strong>dirname, './Scripts/dist'),
                filename: isProduction ? '[name].build.min.js':'[name].build.js'
            },
            module: {
                rules: [
                    {
                        test: /.ts$/,
                        use: 'ts-loader',
                        exclude: /node_modules/
                    }
                ]
            }
        }
    };</p>
<p>The first thing we did was get a reference to the <code>webpack</code> <code>npm</code> package. Remember, <code>webpack.config.js</code> is just a <code>JavaScript</code> file.</p>
<p>We changed the <code>module.exports</code> assigment from a <code>JavaScript</code> object to a function that returns a <code>JavaScript</code> object. This allows us to accept command line parameters that we'll use to set the build environment.</p>
<p>We added a <code>plugins</code> section to our configuration. Here we're toggling the <code>UglifyJsPlugin</code> on or off based on if we're in production mode or not. We're also doing something similar with the <code>filename</code> setting in the <code>output</code> section.</p>
<p>Next we'll update the <code>package.json</code> file to pass the environment settings to <code>webpack</code>.</p>
<p>    {
      "devDependencies": {
        "ts-loader": "^2.2.2",
        "typescript": "^2.4.1",
        "webpack": "^3.2.0"
      },
      "scripts": {
        "build-dev": "webpack",
        "build-prod": "webpack --env=prod"
      }
    }</p>
<p>We now have two <code>npm</code> build scripts for generating bundles, so we'll have to update the <code>.csproj</code> file too if you want to maintain that F5 experience.</p>
     
       

       
     



<p>Now you can toggle between Debug and Release mode in Visual Studio, and <code>MSBuild</code> will kick off <code>webpack</code> to create your bundles.</p>
<p>One thing you might notice, is that the <code>MSBuild</code> tasks doesn't run if there haven't been any changes in your .NET code since the last successful build. You can get around this buy just running the <code>npm</code> scripts directly by firing up the command line or you can install the handy <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.NPMTaskRunner&amp;ref=cecilphillip.com">NPM Task Runner</a> extension from Mads Kristensen.</p>
<h4 id="heading-conclusion">Conclusion</h4>
<p>ASP.NET Web Forms is still a viable option for creating web applications. In this post you saw how to integrate <code>webpack</code> into your projects. Hopefully you'll take that further and figure out how to incorporate other technologies like <a target="_blank" href="https://vuejs.org/?ref=cecilphillip.com">VueJS</a>, <a target="_blank" href="http://aurelia.io/?ref=cecilphillip.com">Aurelia</a> or <a target="_blank" href="https://facebook.github.io/react/?ref=cecilphillip.com">React</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Excluding the node_modules folder when publishing ASP.NET projects.]]></title><description><![CDATA[There are quite a few ASP.NET developers that have been adopting NodeJS in their projects for client side package management and development time tooling. A question that I get relatively often is, how can they exclude the node_modules folder from be...]]></description><link>https://cecilphillip.dev/excluding-the-nodemodules-folder-when-publishing-aspnet-projects</link><guid isPermaLink="true">https://cecilphillip.dev/excluding-the-nodemodules-folder-when-publishing-aspnet-projects</guid><category><![CDATA[ASP.NET]]></category><category><![CDATA[Gulp]]></category><category><![CDATA[msbuild]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Fri, 09 Jun 2017 01:59:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341124370/e8f55710-20dd-4317-8b33-6b4a4a375935.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are quite a few ASP.NET developers that have been adopting <a target="_blank" href="https://nodejs.org/?ref=cecilphillip.com">NodeJS</a> in their projects for client side package management and development time tooling. A question that I get relatively often is, how can they exclude the <code>node_modules</code> folder from being published. In this short post, I'll show some options for getting that done.</p>
<h4 id="heading-setting-up-a-project">Setting up a project</h4>
<p>I'm starting off with an out of the box, new ASP.NET MVC application. If you're using ASP.NET Web Forms then the process should be pretty much the same.</p>
<p>First, let's initialize a <code>package.json</code> file and add some npm packages. You will need to have node installed on your machine. You can grab an installer for your OS on their <a target="_blank" href="https://nodejs.org/en/download/?ref=cecilphillip.com">downloads page</a>. Next, open up a command line prompt at the root of your project.</p>
<blockquote>
<p>To get to the command prompt quickly in Visual Studio, I recommend installing Mads Kristensen's <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.OpenCommandLine&amp;ref=cecilphillip.com">Open Command Line</a> extension. I use it all the time.</p>
</blockquote>
<p>After the <code>package.json</code> file is created, make sure you include it in your project. As you might already know, Visual Studio does not automatically add files that are placed in the project folder.</p>
<p>    $ npm init
    $ npm install toastr --save</p>
<p>Here I'm just installing <code>toastr</code>, it's a handy little JavaScript library for going growl like notifications. In the <code>Scripts</code> folder, I'll create a simple script called <code>app.js</code> that'll create a notification.</p>
<p>    toastr.info("this is just a test message");</p>
<p>Now, the <code>toastr</code> JavaScript and CSS files need to be added to the page. Currently, they've both been installed into the <code>node_modules</code> folder. Instead of referencing that path, I'll create a simple <a target="_blank" href="http://gulpjs.com/?ref=cecilphillip.com">Gulp</a> script that I can use to copy the toastr files out, and do any other processing I'd need for my site's front-end assets.</p>
<p>First, make sure that <code>Gulp</code> is installed on the machine and in the project.</p>
<p>    $ npm install --global gulp-cli
    $ npm install --save-dev gulp gulp-concat</p>
<p>Now create the <code>gulpfile.js</code> file.</p>
<p>    const gulp = require('gulp');
    const concat = require('gulp-concat');</p>
<p>    const paths = {
        nodeModules: './node_modules/',
        scriptsDest: './Scripts/',
        stylesDest: './Content/'
    };</p>
<p>    gulp.task('copy:css', () =&gt; {
        const cssToCopy = [
            <code>${paths.nodeModules}toastr/build/toastr.min.css</code>
        ];</p>
<p>        return gulp.src(cssToCopy)<br />            .pipe(concat('vendor.css'))
            .pipe(gulp.dest(<code>${paths.stylesDest}</code>));
    });</p>
<p>    gulp.task('copy:js', () =&gt; {
        const javascriptToCopy = [
            <code>${paths.nodeModules}toastr/build/toastr.min.js</code>
        ];</p>
<p>        return gulp.src(javascriptToCopy)
            .pipe(concat('vendor.js'))
            .pipe(gulp.dest(<code>${paths.scriptsDest}</code>));
    });</p>
<p>    gulp.task('default',['copy:css', 'copy:js'])</p>
<p>There are 2 simple <code>Gulp</code> tasks here. One to copy the CSS files to the <code>Content</code> folder and the other copies Javascript files to the <code>Scripts</code> folder. To keep it simple, only the <code>toastr</code> files are being copied over but you can add any other dependencies you've installed.</p>
<p>Running <code>gulp</code> on the command line returns some output that looks like this.<br /><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341119877/699a5970-5ba8-498b-aeaf-56cbf16e51db.png" alt /></p>
<p>Since these generated files aren't a part of the project, Visual Studio won't show them in the Solution Explorer by default. At the top of the Solution Explorer, click on the <code>Show All Files</code> icon. You should see <code>vendor.css</code> and <code>vendor.js</code> greyed out under their respective folders. Right click on each file and choose <code>Include In Project</code>.</p>
<p>Now you can just reference the files in your Razor template (or MasterPage if you're working with Web Forms).<br /><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341121309/b376805a-5c7e-4167-8e95-29320bb9d5e7.png" alt /></p>
<h4 id="heading-excluding-nodemodules-from-the-project">Excluding node_modules from the project</h4>
<p>If you followed through the last section and didn't skip ahead, you might have noticed that we included <code>package.json</code>, <code>vendor.js</code> and <code>vendor.css</code> but not the <code>node_modules</code> folder. Simply, excluding the <code>node_modules</code> folder from the project will exclude it from the published web application.</p>
<h4 id="heading-excluding-nodemodules-via-msbuild">Excluding node_modules via MSBUILD</h4>
<p>You might have your own reasons why you want to include the <code>node_modules</code> folder in the project. In that case, you exclude <code>node_modules</code> (or any other folder) from being published by using the <code>ExcludeFromPackageFolders</code> element.</p>
<p>First you'll need to unload your project from the solution and edit the project file. Next add an <code>ExcludeFromPackageFolders</code> element to an ItemGroup element. Using the <code>Include</code> attribute, you can provide a semicolon-separated list of folders that you want to exclude. For now, we're only interested in the <code>node_modules</code> folder.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341122304/a777edc4-9099-4c63-82da-e7b8ef2a35cc.png" alt /></p>
<p>Note that we also include a <code>FromTarget</code> element in our setup. This is an optional element and is only really there for descriptive purposes.</p>
<p>Once that's done, we'll save the file and reload the project. Doing a publish should now exclude the <code>node_modules</code> folder even though it's included in the project.</p>
<h4 id="heading-excluding-files">Excluding files</h4>
<p>With our current setup, the published output also includes<code>package.json</code> and <code>gulpfile.js</code>. If you want to exclude these files from your published output, you can use a similar technique to the previous one using a <code>ExcludeFromPackageFiles</code> element. The <code>Include</code> property even supports globbing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709341123416/2bd22fd8-953d-4677-afea-15e8327964de.png" alt /></p>
<p>Now with all this in place, your NodeJS artifacts will not get included in your published web application.</p>
<h4 id="heading-conclusion">Conclusion</h4>
<p>If you're like me, you prefer to keep your published output at lean as possible. Excluding the <code>node_modules</code> folder can greatly reduce the size of your deployed web application.</p>
<p>In general, I prefer not to let my project file know about the <code>node_modules</code> folder at all. I find that projects load faster and I don't have to worry about ridiculously large commit messages in source control.</p>
]]></content:encoded></item><item><title><![CDATA[Refactoring dependencies with Autofac Aggregate Services]]></title><description><![CDATA[If you make considerable usage of constructor injection and the dependency inversion principle in your applications, you may eventually run into a situation where you have a fairly long list of dependencies that need to get provided to your class.
Mo...]]></description><link>https://cecilphillip.dev/refactoring-dependencies-with-autofac-aggregate-services</link><guid isPermaLink="true">https://cecilphillip.dev/refactoring-dependencies-with-autofac-aggregate-services</guid><category><![CDATA[AutoFac,]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Wed, 31 May 2017 19:57:33 GMT</pubDate><content:encoded><![CDATA[<p>If you make considerable usage of constructor injection and the <a target="_blank" href="https://en.wikipedia.org/wiki/Dependency_inversion_principle?ref=cecilphillip.com">dependency inversion principle</a> in your applications, you may eventually run into a situation where you have a fairly long list of dependencies that need to get provided to your class.</p>
<p>More often than not, this may be the cause of your class trying to be responsible for too much functionality. At this point, you'd probably take the route of breaking out some of that functionality into another class or service. Another reason for a growing dependency list might be due to your class' inheritance hierarchy. The parent class might require its own set of dependencies to be injected, and this would require the sub-classes to pass on these dependencies to their parent. What would happen if updates to the parent class add additional dependencies? All of sub-classes now would need to be updated to provide these also.</p>
<p>In this short post we will explore the option of using aggregate services to clean up our code and also shield our sub-classes from changes in the parent class dependencies.</p>
<h5 id="heading-whats-an-aggregate-service">What's an Aggregate Service?</h5>
<p>What an aggregate service allows you to do is treat a collection of dependencies as a single dependency. As you can image, if a class depends on having multiple classes or services injected into it, we can simplify its API by moving these dependencies into a single one.</p>
<p>Imagine we have some code that looks something like this.</p>
<p>    public class OrderRequestHandler : BaseRequestHandler
    {
        private IPaymentProcessor _paymentProcessor;</p>
<p>        public OrderRequestHandler(IPaymentProcessor paymentProcessor,
                                   ILogger logger,
                                   IValidator validator) : base(logger, validator)
        {
            _paymentProcessor = paymentProcessor;
        }
    }</p>
<p>    public class BaseRequestHandler
    {
        private readonly ILogger _logger;
        private IValidator _validator;
        public BaseRequestHandler(ILogger logger, IValidator validator)
        {
            _logger = logger;
            _validator = validator;
        }
    }</p>
<p>The <code>OrderRequestHandler</code> class takes in three dependencies. Two of those dependencies are needed by the parent class. Adding another dependency to the parent, like <code>IDataStore</code> for instance, will require all of its sub-classes to be updated.</p>
<p>    public class OrderRequestHandler : BaseRequestHandler
    {
        private IPaymentProcessor _paymentProcessor;</p>
<p>        public OrderRequestHandler(IPaymentProcessor paymentProcessor, 
                                   ILogger logger,
                                   IValidator validator,
                                   IDataStore dataStore) 
                                   : base(logger, validator, dataStore)
        {
            _paymentProcessor = paymentProcessor;
        }
    }</p>
<p>    public class BaseRequestHandler
    {
        private readonly ILogger _logger;
        private IValidator _validator;
        private IDataStore _dataStore;</p>
<p>        public BaseRequestHandler(ILogger logger,
                                  IValidator validator,
                                  IDataStore dataStore)
        {
            _logger = logger;
            _validator = validator;
            _dataStore = dataStore;
        }
    }</p>
<p>Using aggregate services, we can introduce another abstraction to contain some of these "core services" that will get passed on to the parent class. In the code below, we are using a simple interface to model the aggregate service.</p>
<p>     public interface ICoreServices {
          IValidator Validator {get;}
          IDataStore DataStore {get;}
     }</p>
<h5 id="heading-implementing-aggregate-services-with-autofac">Implementing Aggregate Services with Autofac</h5>
<p>Implementing an aggregate service by hand is relatively trivial to accomplish. This would require building a class that accepts the dependencies as constructor arguments and then exposing those dependencies as properties.</p>
<p>Even though this is not hard to do, Autofac provides the handy <a target="_blank" href="https://www.nuget.org/packages/Autofac.Extras.AggregateService?ref=cecilphillip.com">Autofac.Extras.AggregateService</a> NuGet package that will wire all of this up for you. After installing the package you will have a <code>RegisterAggregateService</code> method available off of <code>ContainerBuilder</code>, which is what we'll use to set things up. Using the code samples above, the <code>Autofac</code> registration code would look like this.</p>
<p>    builder.RegisterAggregateService();
    builder.RegisterType().As();
    builder.RegisterType().As();
    builder.RegisterType().As();</p>
<p>Now we can update the <code>OrderRequestHandler</code> and <code>BaseRequestHandler</code> classes.</p>
<p>     public class OrderRequestHandler : BaseRequestHandler
     {
         private IPaymentProcessor _paymentProcessor;</p>
<p>         public OrderRequestHandler(IPaymentProcessor paymentProcessor, 
                                    ICoreServices coreServices)
                                    : base(coreServices)
         {
             _paymentProcessor = paymentProcessor;
         }
     }</p>
<p>     public class BaseRequestHandler
     {
         private ICoreServices _coreServices;</p>
<p>         public BaseRequestHandler(ICoreServices coreServices)
         {
             _coreServices = coreServices;
         }
     }</p>
<p>What's happening behind the scenes is that the <code>Autofac.Extras.AggregateService</code> is generating a proxy for the <code>ICoreServices</code> interface using <a target="_blank" href="http://www.castleproject.org/projects/dynamicproxy/?ref=cecilphillip.com">DynamicProxy</a>. On running the application, the generated proxy will get injected into <code>OrderRequestHandler</code>. When a property is requested, it will be resolved from the <code>Autofac</code> container.</p>
<h5 id="heading-conclusion">Conclusion</h5>
<p>Implementing Aggregate Services is far from a silver bullet solution to cleaning up a large number of dependencies, but I think it's an interesting option to explore. Take a look at the following links for some further discussion on the topic.</p>
<ul>
<li><a target="_blank" href="http://peterspattern.com/dependency-injection-and-class-inheritance?ref=cecilphillip.com">Dependency Injection and Class Inheritance</a></li>
<li><a target="_blank" href="http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/?ref=cecilphillip.com">Refactoring to Aggregate Services</a></li>
</ul>
<p>If you want to learn more about <code>Autofac</code> and its <code>Aggregate Services</code> extension, head over to their website and check out the <a target="_blank" href="http://docs.autofac.org/en/latest/advanced/aggregate-services.html?ref=cecilphillip.com">documentation</a> site.</p>
]]></content:encoded></item></channel></rss>