<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Akalanka Dissanayake on Medium]]></title>
        <description><![CDATA[Stories by Akalanka Dissanayake on Medium]]></description>
        <link>https://medium.com/@disa2aka?source=rss-e43e08829d98------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*fTDV8J9xNtPFC3PG8WwWYA.png</url>
            <title>Stories by Akalanka Dissanayake on Medium</title>
            <link>https://medium.com/@disa2aka?source=rss-e43e08829d98------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 22 May 2026 13:51:01 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@disa2aka/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[ Don’t Fail-Safe Until You Have To]]></title>
            <link>https://medium.com/@disa2aka/dont-fail-safe-until-you-have-to-0211a7599f23?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/0211a7599f23</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[learning-to-code]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Mon, 21 Jul 2025 03:08:55 GMT</pubDate>
            <atom:updated>2025-07-21T03:08:55.747Z</atom:updated>
            <content:encoded><![CDATA[<p>We developers love being safe.</p><ul><li>We check for null even if we expect a value.</li><li>We wrap everything in try/catch just in case.</li><li>We add fallbacks, guards, and defaults — before the need ever arises.</li></ul><p>This is called <strong>fail-safe programming</strong>.</p><p>And while it <em>feels</em> responsible, it can actually lead to code that’s noisy, hard to maintain, and less honest about what’s really going on.</p><h3>⚠️ The Problem with Fail-Safe by Default</h3><p>Fail-safe logic is <strong>expensive</strong> when overused:</p><ul><li>❌ It hides bugs instead of surfacing them</li><li>❌ It bloats otherwise clear logic</li><li>❌ It encourages defensive habits over intentional design</li></ul><p>Instead of writing code to protect yourself from the unknown, write code that reflects what your system <em>knows to be true</em> — until a real business case says otherwise.</p><h3>🔧 Example: Updating an Entity with EF Core</h3><p>Let’s start with this typical fail-safe update method:</p><pre>public async Task&lt;Result&gt; UpdateProductAsync(int id, ProductUpdateDto dto)<br>{<br>    try<br>    {<br>        var product = await _dbContext.Products.FindAsync(id);<br>        if (product == null)<br>            return Result.Failure(&quot;Product not found&quot;);<br><br>        product.Name = dto.Name;<br>        product.Price = dto.Price;<br>        await _dbContext.SaveChangesAsync();<br><br>        return Result.Success();<br>    }<br>    catch<br>    {<br>        return Result.Failure(&quot;Something went wrong.&quot;);<br>    }<br>}</pre><p>This is <strong>safe</strong> — but also <strong>too cautious</strong>.</p><p>It assumes:</p><ul><li>The ID might be wrong</li><li>The query might throw</li><li>We must handle everything <em>right here</em></li></ul><p>But if the flow is trusted (say the ID came from a known dropdown or route), this is overkill.</p><h3>✅ Same Operation, Less Fear</h3><pre>public async Task UpdateProductAsync(int id, ProductUpdateDto dto)<br>{<br>    var product = await _dbContext.Products.SingleAsync(p =&gt; p.Id == id);<br>    product.Name = dto.Name;<br>    product.Price = dto.Price;<br>    await _dbContext.SaveChangesAsync();<br>}</pre><p>Now if something goes wrong — it <strong>fails</strong>, as it should.</p><p>And instead of catching locally, we <strong>let global error handling handle the unexpected</strong>.</p><p>That’s not reckless — that’s clarity.</p><h3>🧠 Fail-Safe Code Is Not Evil — Just Overused</h3><p>There are <strong>valid reasons</strong> to write defensive code.</p><p>✅ Use fail-safes <strong>when:</strong></p><ul><li>The failure is expected (e.g., external APIs, user input, timeouts)</li><li>The failure is part of business logic (e.g., show “Product not found” if the user needs to see it)</li><li>The recovery improves the experience (e.g., “Try again” buttons)</li></ul><p>❌ Avoid fail-safes <strong>when:</strong></p><ul><li>You’re defending against internal data that <em>should</em> be valid</li><li>You’re catching errors just to suppress them</li><li>You’re guarding because “that’s what we always do”</li></ul><h3>🤯 Real-World Fail-Safe Overkill</h3><h3>🔴 React: Over-defensive rendering</h3><pre>function ProductCard({ product }: { product: Product }) {<br>  if (!product) return null;<br>  return &lt;div&gt;{product.name}&lt;/div&gt;;<br>}</pre><p>Why render the component if product is required?</p><h3>✅ Better (if contract says it’s required)</h3><pre>function ProductCard({ product }: { product: Product }) {<br>  return &lt;div&gt;{product.name}&lt;/div&gt;;<br>}</pre><h3>🔴 JavaScript: Guarding when you own the value</h3><pre>const name = response?.data?.user?.name ?? &quot;Unknown&quot;;</pre><h3>✅ Better: Trust the API contract or validate once</h3><pre>const name = response.data.user.name;</pre><blockquote><em>Let it throw — and let your </em><strong><em>global error handler</em></strong><em> deal with the unexpected.</em></blockquote><h3>✅ Your Rule of Thumb</h3><blockquote><strong><em>Don’t write fail-safe code unless the business, domain, or user experience explicitly requires it.</em></strong></blockquote><p>Write code for what should happen — not what might go wrong in theory.</p><p>When failure matters, handle it.<br> When it doesn’t — let the system do its job.</p><h3>🧵 Final Thought</h3><p>Fail-safe programming is a useful tool. But used everywhere, it becomes a crutch.</p><p>Not all errors need to be caught.<br> Not all values need to be checked.<br> Not all flows need fallback logic.</p><p>Write code with confidence.<br> <strong>Let failures bubble. Let global handlers do their job.<br> Fail when it makes sense — not because you’re afraid.</strong></p><h4>🚀 Happy coding!!!</h4><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0211a7599f23" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Level Up Your Dapper Game: Write Better, Safer Data Code with Ease]]></title>
            <link>https://medium.com/c-sharp-programming/level-up-your-dapper-game-write-better-safer-data-code-with-ease-9cf0d5c9cf23?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/9cf0d5c9cf23</guid>
            <category><![CDATA[dapper]]></category>
            <category><![CDATA[curd]]></category>
            <category><![CDATA[unitofwork]]></category>
            <category><![CDATA[sqlbulkcopy]]></category>
            <category><![CDATA[dotnet]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Thu, 27 Mar 2025 03:11:56 GMT</pubDate>
            <atom:updated>2025-03-27T14:49:58.085Z</atom:updated>
            <content:encoded><![CDATA[<p>Dapper is amazing for its speed and simplicity — but it’s also barebones. If you’ve worked with Entity Framework, you might miss features like change tracking, auditing, and Unit of Work support.</p><p>This article shows you how to take Dapper to the next level by:</p><ul><li>Wrapping Dapper in a clean Unit of Work abstraction</li><li>Automatically handling Created/Modified timestamps and users</li><li>Creating a pluggable, testable, and production-grade data access layer</li></ul><p><a href="https://github.com/andissanayake/UOW">Source code</a></p><h3>The Pain Points of Raw Dapper</h3><p>Dapper is super fast, but…</p><ul><li>❌ No built-in transaction scope</li><li>❌ No auditing support (CreatedBy, ModifiedBy)</li><li>❌ No unified place to commit/rollback</li><li>❌ No easy way to plug in user context</li><li>❌ No bulk insert support</li></ul><h3>Usage Comparison</h3><h3>Raw Dapper (Insert Example)</h3><pre>using var connection = new SqlConnection(&quot;...connectionString...&quot;);<br>await connection.OpenAsync();<br><br>var user = new User { Id = Guid.NewGuid(), Name = &quot;Alex&quot; };<br>await connection.ExecuteAsync(<br>    &quot;INSERT INTO Users (Id, Name) VALUES (@Id, @Name)&quot;,<br>    user);</pre><h3>UoW Wrapper (Insert Example)</h3><p>Powered by Dapper.Contrib</p><pre>using var uow = _unitOfWorkFactory.CreateUOW();<br><br>var user = new User { Name = &quot;Alex&quot; };<br>await uow.InsertAsync(user);<br>uow.Commit();</pre><h3>UoW Wrapper (Get By Id)</h3><p>Powered by Dapper.Contrib</p><pre>using var uow = _unitOfWorkFactory.CreateUOW();<br>var user = await uow.GetAsync&lt;User&gt;(userId);<br>uow.Commit();</pre><h3>UoW Wrapper (Update Example)</h3><p>Powered by Dapper.Contrib</p><pre>using var uow = _unitOfWorkFactory.CreateUOW();<br><br>var user = await uow.GetAsync&lt;User&gt;(userId);<br>user.Name = &quot;Updated Name&quot;;<br>await uow.UpdateAsync(user);<br>uow.Commit();</pre><h3>UoW Wrapper (Bulk Copy Example)</h3><pre>using var uow = _unitOfWorkFactory.CreateUOW();<br><br>var users = Enumerable.Range(1, 1000).Select(i =&gt; new User<br>{<br>    Id = Guid.NewGuid(),<br>    Name = $&quot;User {i}&quot;<br>});<br>await uow.BulkCopyAsync(users);<br>uow.Commit();</pre><ul><li>Cleaner and consistent syntax</li><li>Audit fields are handled automatically</li><li>Shared transaction for safety</li></ul><h3>Introducing the Architecture</h3><p>We’ll use these key building blocks:</p><ul><li>BaseEntity — for audit fields</li><li>ICurrentUserService — abstraction for current user context</li><li>IUnitOfWork — transactional Dapper wrapper</li><li>UnitOfWork — Dapper + audit logic + transaction + bulk copy</li><li>UnitOfWorkFactory — for creating multiple UoW instances per method</li></ul><h3>Step 1: The BaseEntity with Audit Fields</h3><pre>using Dapper.Contrib.Extensions;<br><br>public class BaseEntity&lt;TID&gt; : AuditableEntity<br>{<br>    [Key]<br>    public TID Id { get; set; } = default!;<br>}<br>public class AuditableEntity<br>{<br>    public DateTimeOffset Created { get; set; }<br>    public string CreatedBy { get; set; } = default!;<br>    public DateTimeOffset? LastModified { get; set; }<br>    public string? LastModifiedBy { get; set; }<br>}</pre><h3>Step 2: The Current User Context</h3><pre>public interface ICurrentUserService<br>{<br>    public string UserId { get; }<br>}<br>public class CurrentUserService : ICurrentUserService<br>{<br>    public string UserId =&gt; &quot;SYSTEM&quot;; // Replace with real claims logic<br>}</pre><h3>Step 3: The Unit of Work Interface</h3><pre>public interface IUnitOfWork : IDisposable<br>{<br>    IDbConnection Connection { get; }<br>    Task InsertAsync&lt;T&gt;(T entity) where T : class;<br>    Task UpdateAsync&lt;T&gt;(T entity) where T : class;<br>    Task DeleteAsync&lt;T&gt;(T entity) where T : class;<br>    Task&lt;T?&gt; GetAsync&lt;T&gt;(object id) where T : class;<br>    Task&lt;IEnumerable&lt;T&gt;&gt; GetAllAsync&lt;T&gt;() where T : class;<br>    Task&lt;IEnumerable&lt;T&gt;&gt; QueryAsync&lt;T&gt;(string sql, object? param = null);<br>    Task&lt;T?&gt; QueryFirstOrDefaultAsync&lt;T&gt;(string sql, object? param = null);<br>    Task&lt;T&gt; QuerySingleAsync&lt;T&gt;(string sql, object? param = null);<br>    Task&lt;int&gt; ExecuteAsync(string sql, object? param = null);<br>    Task BulkCopyAsync&lt;T&gt;(IEnumerable&lt;T&gt; items, string? tableName = null);<br>    void Commit();<br>    void Rollback();<br>}</pre><h3>Step 4: The Unit of Work Implementation</h3><pre>public class UnitOfWork : IUnitOfWork<br>{<br>    public IDbConnection Connection { get; }<br>    private IDbTransaction? _transaction;<br>    private bool _isCompleted;<br>    private readonly ICurrentUserService _currentUserService;<br>  <br>    public UnitOfWork(IDbConnection connection, ICurrentUserService currentUserService, bool transactional = true)<br>    {<br>        SqlMapperExtensions.TableNameMapper = (type) =&gt; type.Name;<br>        _currentUserService = currentUserService;<br>        Connection = connection;<br>        Connection.Open();<br>        if (transactional)<br>        {<br>            _transaction = Connection.BeginTransaction();<br>        }<br>        else<br>        {<br>            _isCompleted = true;<br>        }<br>    }<br>    <br>    public async Task InsertAsync&lt;T&gt;(T entity) where T : class<br>    {<br>        if (entity is BaseEntity auditable)<br>        {<br>            var now = DateTimeOffset.UtcNow;<br>            auditable.Created = now;<br>            auditable.CreatedBy = _currentUserService.UserId;<br>        }<br>        await Connection.InsertAsync(entity, _transaction);<br>    }<br>    <br>    public async Task UpdateAsync&lt;T&gt;(T entity) where T : class<br>    {<br>        if (entity is BaseEntity auditable)<br>        {<br>            auditable.LastModified = DateTimeOffset.UtcNow;<br>            auditable.LastModifiedBy = _currentUserService.UserId;<br>        }<br>        await Connection.UpdateAsync(entity, _transaction);<br>    }<br>    <br>    public async Task DeleteAsync&lt;T&gt;(T entity) where T : class =&gt;<br>        await Connection.DeleteAsync(entity, _transaction);<br><br>    public async Task&lt;T?&gt; GetAsync&lt;T&gt;(object id) where T : class =&gt;<br>        await Connection.GetAsync&lt;T&gt;(id, _transaction);<br><br>    public async Task&lt;IEnumerable&lt;T&gt;&gt; GetAllAsync&lt;T&gt;() where T : class =&gt;<br>        await Connection.GetAllAsync&lt;T&gt;(_transaction);<br><br>    public async Task&lt;IEnumerable&lt;T&gt;&gt; QueryAsync&lt;T&gt;(string sql, object? param = null) =&gt;<br>        await Connection.QueryAsync&lt;T&gt;(sql, param, _transaction);<br>    <br>    public async Task&lt;T&gt; QuerySingleAsync&lt;T&gt;(string sql, object? param = null) =&gt;<br>        await Connection.QuerySingleAsync&lt;T&gt;(sql, param, _transaction);<br>    <br>    public async Task&lt;T?&gt; QueryFirstOrDefaultAsync&lt;T&gt;(string sql, object? param = null) =&gt;<br>        await Connection.QueryFirstOrDefaultAsync&lt;T&gt;(sql, param, _transaction);<br>    <br>    public async Task&lt;int&gt; ExecuteAsync(string sql, object? param = null) =&gt;<br>        await Connection.ExecuteAsync(sql, param, _transaction);<br>    <br>    public async Task BulkCopyAsync&lt;T&gt;(IEnumerable&lt;T&gt; items, string? tableName = null)<br>    {<br>        if (Connection is not SqlConnection sqlConnection)<br>            throw new NotSupportedException(&quot;Bulk insert is only supported with SqlConnection.&quot;);<br>        var actualTableName = tableName ?? typeof(T).Name;<br>        var props = typeof(T)<br>            .GetProperties(BindingFlags.Public | BindingFlags.Instance)<br>            .Where(p =&gt; p.CanWrite)<br>            .ToArray();<br>        var dataTable = new DataTable();<br>        foreach (var prop in props)<br>        {<br>            var type = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;<br>            if (type == typeof(DateTimeOffset))<br>                type = typeof(DateTime);<br>            dataTable.Columns.Add(prop.Name, type);<br>        }<br>        var now = DateTimeOffset.UtcNow;<br>        var userId = _currentUserService.UserId ?? &quot;system&quot;;<br>        foreach (var item in items)<br>        {<br>            var idProp = typeof(T).GetProperty(&quot;Id&quot;);<br>            if (idProp != null &amp;&amp; idProp.PropertyType == typeof(Guid))<br>            {<br>                var idValue = (Guid?)idProp.GetValue(item);<br>                if (idValue == null || idValue == Guid.Empty)<br>                {<br>                    idProp.SetValue(item, Guid.NewGuid());<br>                }<br>            }<br>            if (item is BaseEntity auditable)<br>            {<br>                auditable.Created = now;<br>                auditable.CreatedBy = userId;<br>            }<br>            var values = props.Select(p =&gt;<br>            {<br>                var value = p.GetValue(item);<br>                if (value is DateTimeOffset dto)<br>                    return dto.UtcDateTime;<br>                return value ?? DBNull.Value;<br>            }).ToArray();<br>            dataTable.Rows.Add(values);<br>        }<br>        using var bulkCopy = new SqlBulkCopy(sqlConnection, SqlBulkCopyOptions.Default, (SqlTransaction?)_transaction)<br>        {<br>            DestinationTableName = actualTableName<br>        };<br>        foreach (DataColumn column in dataTable.Columns)<br>        {<br>            bulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);<br>        }<br>        await bulkCopy.WriteToServerAsync(dataTable);<br>    }<br>    public void Commit()<br>    {<br>        if (_isCompleted) return;<br>        _transaction?.Commit();<br>        _isCompleted = true;<br>    }<br>    public void Rollback()<br>    {<br>        if (_isCompleted) return;<br>        _transaction?.Rollback();<br>        _isCompleted = true;<br>    }<br>    public void Dispose()<br>    {<br>        if (!_isCompleted)<br>        {<br>            try { _transaction?.Rollback(); } catch { }<br>        }<br>        _transaction?.Dispose();<br>        Connection.Dispose();<br>    }<br>}</pre><h3>Step 5: The Factory</h3><pre>public interface IUnitOfWorkFactory<br>{<br>    public IUnitOfWork CreateUOW();<br>}<br><br>public class UnitOfWorkFactory : IUnitOfWorkFactory<br>{<br>    private readonly string _connectionString;<br>    private readonly ICurrentUserService _currentUserService;<br>    public UnitOfWorkFactory(string connectionString, ICurrentUserService currentUserService)<br>    {<br>        _connectionString = connectionString;<br>        _currentUserService = currentUserService;<br>    }<br>    public IUnitOfWork CreateUOW()<br>    {<br>        var connection = new SqlConnection(_connectionString);<br>        return new UnitOfWork(connection, _currentUserService);<br>    }<br>}</pre><h3>Dependency Injection Setup (ASP.NET Core)</h3><pre>builder.Services.AddHttpContextAccessor();<br>builder.Services.AddScoped&lt;ICurrentUserService, CurrentUserService&gt;();<br>builder.Services.AddScoped&lt;IUnitOfWorkFactory&gt;(provider =&gt;<br>{<br>    var config = provider.GetRequiredService&lt;IConfiguration&gt;();<br>    var user = provider.GetRequiredService&lt;ICurrentUserService&gt;();<br>    return new UnitOfWorkFactory(config.GetConnectionString(&quot;DefaultConnection&quot;), user);<br>});</pre><h3>Benefits</h3><ul><li>Clean separation of concerns</li><li>Transaction-safe Dapper access</li><li>Auditing baked in automatically</li><li>Fast SqlBulkCopy support for batch inserts</li><li>Pluggable and testable</li></ul><p>Happy coding! 💙</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9cf0d5c9cf23" width="1" height="1" alt=""><hr><p><a href="https://medium.com/c-sharp-programming/level-up-your-dapper-game-write-better-safer-data-code-with-ease-9cf0d5c9cf23">Level Up Your Dapper Game: Write Better, Safer Data Code with Ease</a> was originally published in <a href="https://medium.com/c-sharp-programming">.Net Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Does using Actually Dispose the Connection? (Understanding Connection Pooling in Dapper)]]></title>
            <link>https://medium.com/c-sharp-programming/does-using-actually-dispose-the-connection-understanding-connection-pooling-in-dapper-b4320ae2c643?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/b4320ae2c643</guid>
            <category><![CDATA[memory-management]]></category>
            <category><![CDATA[c-sharp-programming]]></category>
            <category><![CDATA[dapper]]></category>
            <category><![CDATA[adonet]]></category>
            <category><![CDATA[dotnet]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Tue, 18 Mar 2025 15:03:24 GMT</pubDate>
            <atom:updated>2025-03-25T22:21:10.891Z</atom:updated>
            <content:encoded><![CDATA[<p>When working with Dapper and Microsoft.Data.SqlClient, a common question is:</p><blockquote>Does the connection actually get disposed after using using — or does connection pooling change how it works?</blockquote><p>Example:</p><pre>using (var connection = new SqlConnection(connectionString))<br>{<br>    connection.Open(); // Pulls from the pool if available<br>    var data = connection.Query(&quot;SELECT * FROM Products&quot;);<br>} // Connection is returned to the pool</pre><p>This article explains why the using statement is essential even when connection pooling is enabled (this is the <strong>default behavior</strong>).</p><h3>How Connection Pooling Works</h3><p>When you create a connection using new SqlConnection() and call Open(), the connection pool manager follows these steps:</p><ol><li>If an <strong>available connection</strong> exists in the pool, it will reuse that connection instead of creating a new one.</li><li>If <strong>no available connection</strong> exists in the pool, it will create a <strong>new physical connection</strong> to the database.</li><li>When the connection is closed or disposed, it is <strong>returned to the pool</strong> instead of being physically closed.</li></ol><p>This mechanism reduces the overhead of opening and closing physical connections, improving performance.</p><blockquote><em>🔎 </em><strong><em>Connection pooling is enabled by default</em></strong><em> in </em><em>Microsoft.Data.SqlClient.</em></blockquote><h3>Why using is Still Necessary</h3><p>Even though connection pooling handles connection reuse, the using statement (or explicitly calling Dispose()) is still essential for several reasons:</p><h4>1. Ensures Connection is Returned to the Pool</h4><ul><li>Dispose() signals to the connection pool manager that the connection is no longer in use.</li><li>Without Dispose() or Close(), the connection would remain &quot;checked out&quot; from the pool, reducing the number of available connections.</li></ul><blockquote><em>🚀 Failing to return the connection promptly can lead to connection pool exhaustion under high load.</em></blockquote><h4>2. Avoids Connection Leaks</h4><ul><li>If you forget to close the connection, it will stay open and consume resources.</li><li>Leaking connections reduces the number of available connections in the pool, causing eventual connection timeouts.</li></ul><p>Bad example:</p><pre>var connection = new SqlConnection(connectionString);<br>connection.Open();<br>// Forgot to close the connection!</pre><p>Good example:</p><pre>using (var connection = new SqlConnection(connectionString))<br>{<br>    connection.Open();<br>    // Code here<br>} // Connection is returned to the pool automatically</pre><h4>3. Handles Exceptions</h4><ul><li>If an exception occurs, the using block ensures that Dispose() is called even if the operation fails.</li><li>This prevents connections from being left in an open state.</li></ul><p>Example:</p><pre>using (var connection = new SqlConnection(connectionString))<br>{<br>    connection.Open();<br>    throw new Exception(&quot;Oops!&quot;); // Still gets disposed<br>}</pre><h4>4. Prevents Connection Starvation</h4><ul><li>When too many connections are left open (by not closing them), new requests will have to wait for an available connection.</li><li>This can lead to degraded performance and timeouts.</li></ul><h4>5. Cleaner and More Maintainable Code</h4><ul><li>using makes the connection lifecycle clear and predictable.</li><li>Without using, you&#39;d need a finally block to call Dispose() manually, which increases complexity and risk of human error.</li></ul><h3>Best Practices for Dapper and Connection Pooling</h3><ul><li>Always use a using block when opening a connection.</li><li>Open the connection <strong>late</strong> and close it <strong>early</strong>.</li><li>Let the pool handle connection reuse.</li><li>Avoid holding connections open longer than necessary.</li></ul><p><strong>Happy Coding!</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b4320ae2c643" width="1" height="1" alt=""><hr><p><a href="https://medium.com/c-sharp-programming/does-using-actually-dispose-the-connection-understanding-connection-pooling-in-dapper-b4320ae2c643">Does using Actually Dispose the Connection? (Understanding Connection Pooling in Dapper)</a> was originally published in <a href="https://medium.com/c-sharp-programming">.Net Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dynamic Sorting in .NET with LinqFlex]]></title>
            <link>https://medium.com/@disa2aka/dynamic-sorting-in-net-with-linqflex-003caddc7712?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/003caddc7712</guid>
            <category><![CDATA[entityframework-core]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[dynamic-sorting]]></category>
            <category><![CDATA[linq]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Fri, 07 Jun 2024 07:50:26 GMT</pubDate>
            <atom:updated>2024-06-07T07:50:26.191Z</atom:updated>
            <content:encoded><![CDATA[<h3>Dynamic Sorting in .NET with LinqFlex</h3><h3>Introduction</h3><p>Sorting data dynamically at runtime can greatly enhance the flexibility of your applications, especially when dealing with user-driven sorting criteria. LinqFlex is a .NET library that enables dynamic ordering of IQueryable collections based on property names or key selectors. In this article, we’ll explore how to use LinqFlex for dynamic sorting in your .NET applications.</p><h3>Installation</h3><p>First, you need to install the LinqFlex package via NuGet. You can do this using the .NET CLI:</p><pre>dotnet add package LinqFlex</pre><p>Or via the NuGet Package Manager in Visual Studio.</p><h4>Setting Up Your Project</h4><p>Ensure you have a basic project set up. For demonstration purposes, let’s assume you have a simple Person class:</p><pre>public class Person<br>{<br>    public int Id { get; set; }<br>    public string Name { get; set; }<br>    public int Age { get; set; }<br>}</pre><h4>Using LinqFlex for Dynamic Sorting</h4><p>One of the primary features of LinqFlex is the ability to order collections dynamically by property name. Here’s how you can do it:</p><pre>using LinqFlex;<br><br>var people = new List&lt;Person&gt;<br>{<br>    new Person { Id = 1, Name = &quot;Alice&quot;, Age = 30 },<br>    new Person { Id = 2, Name = &quot;Bob&quot;, Age = 25 },<br>    new Person { Id = 3, Name = &quot;Charlie&quot;, Age = 35 }<br>}.AsQueryable();<br><br>// Sort by Name in ascending order<br>var sortedByName = people.OrderBy(&quot;Name&quot;, true);<br><br>// Sort by Age in descending order<br>var sortedByAge = people.OrderBy(&quot;Age&quot;, false);<br><br>// Display the sorted results<br>Console.WriteLine(&quot;Sorted by Name:&quot;);<br>foreach (var person in sortedByName)<br>{<br>    Console.WriteLine($&quot;{person.Name} - {person.Age}&quot;);<br>}<br><br>Console.WriteLine(&quot;Sorted by Age:&quot;);<br>foreach (var person in sortedByAge)<br>{<br>    Console.WriteLine($&quot;{person.Name} - {person.Age}&quot;);<br>}</pre><h4>Explanation</h4><p>In the above example, the OrderBy extension method from LinqFlex is used to sort the people collection. By specifying the property name as a string and the sort direction (true for ascending, false for descending), you can dynamically order the collection at runtime.</p><h4>Handling Invalid Properties</h4><p>If you try to sort by a property that doesn’t exist, LinqFlex will throw an ArgumentException. This ensures that your application can handle errors gracefully:</p><pre>try<br>{<br>    var invalidSort = people.OrderBy(&quot;InvalidProperty&quot;, true);<br>}<br>catch (ArgumentException ex)<br>{<br>    Console.WriteLine(ex.Message); // Outputs: &quot;Property &#39;InvalidProperty&#39; does not exist on type &#39;Person&#39;.&quot;<br>}</pre><h3>Conclusion</h3><p>Dynamic sorting is a powerful feature that can enhance the user experience by allowing flexible data manipulation. With LinqFlex, implementing dynamic ordering in your .NET applications becomes straightforward and efficient. Install LinqFlex today and start leveraging its capabilities to provide dynamic sorting in your projects.</p><p>For more information and advanced usage, visit the <a href="https://github.com/andissanayake/LinqFlex">LinqFlex GitHub repository.</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=003caddc7712" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Simplifying Excel Data Processing in C# with ExcelShaper]]></title>
            <link>https://medium.com/@disa2aka/simplifying-excel-data-processing-in-c-with-excelshaper-eeea74f7571f?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/eeea74f7571f</guid>
            <category><![CDATA[excel]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[openxml]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Wed, 22 May 2024 17:41:33 GMT</pubDate>
            <atom:updated>2024-05-22T17:41:51.349Z</atom:updated>
            <content:encoded><![CDATA[<h4>Introduction</h4><p>Working with Excel data in C# can be challenging. To simplify this task, we developed the ExcelShaper NuGet package, providing a straightforward and effective method for reading and processing Excel files.</p><h4>What is ExcelShaper?</h4><p>ExcelShaper is a <a href="https://www.nuget.org/packages/ExcelShaper">NuGet package</a> designed to simplify reading Excel files in C#. It supports reading data by sheet index or headers and allows mapping rows to custom C# objects effortlessly.</p><h4>Installation</h4><p>To install ExcelShaper, run the following command in your project directory:</p><pre>dotnet add package ExcelShaper</pre><p>You can follow <a href="https://github.com/andissanayake/ExcelShaper">GitHub</a> repository examples project</p><h4>Basic Usage</h4><p>Here’s how to set up a C# project to use ExcelShaper. This example reads data from the first sheet by index:</p><pre>using ExcelShaper;<br>using System.Text.Json;<br><br>class Program<br>{<br>    static void Main(string[] args)<br>    {<br>       var ret1 = Engine.ReadExcelFileByIndex(filePath);<br>       Console.WriteLine(JsonSerializer.Serialize(ret1.Take(3)));<br>    }<br>}</pre><pre>[[&quot;Index&quot;,&quot;First Name&quot;,&quot;Last Name&quot;,&quot;Gender&quot;,&quot;Country&quot;,&quot;Age&quot;,&quot;Date&quot;,&quot;Id&quot;],<br>[&quot;1&quot;,&quot;Dulce&quot;,&quot;Abril&quot;,&quot;Female&quot;,&quot;United States&quot;,&quot;32&quot;,&quot;15/10/2017&quot;,&quot;1562&quot;],<br>[&quot;2&quot;,&quot;Mara&quot;,&quot;Hashimoto&quot;,&quot;Female&quot;,&quot;Great Britain&quot;,&quot;25&quot;,&quot;16/08/2016&quot;,&quot;1582&quot;]]</pre><h4>Reading Excel File by Headers</h4><p>To read data using sheet headers, use the following code:</p><pre>var ret4 = Engine.ReadExcelFileByHeader(filePath);<br>Console.WriteLine(JsonSerializer.Serialize(ret4.Take(3)));</pre><pre>[{&quot;index&quot;:&quot;1&quot;,&quot;firstname&quot;:&quot;Dulce&quot;,&quot;lastname&quot;:&quot;Abril&quot;,&quot;gender&quot;:&quot;Female&quot;,&quot;country&quot;:&quot;United States&quot;,&quot;age&quot;:&quot;32&quot;,&quot;date&quot;:&quot;15/10/2017&quot;,&quot;id&quot;:&quot;1562&quot;},<br>{&quot;index&quot;:&quot;2&quot;,&quot;firstname&quot;:&quot;Mara&quot;,&quot;lastname&quot;:&quot;Hashimoto&quot;,&quot;gender&quot;:&quot;Female&quot;,&quot;country&quot;:&quot;Great Britain&quot;,&quot;age&quot;:&quot;25&quot;,&quot;date&quot;:&quot;16/08/2016&quot;,&quot;id&quot;:&quot;1582&quot;},<br>{&quot;index&quot;:&quot;3&quot;,&quot;firstname&quot;:&quot;Philip&quot;,&quot;lastname&quot;:&quot;Gent&quot;,&quot;gender&quot;:&quot;Male&quot;,&quot;country&quot;:&quot;France&quot;,&quot;age&quot;:&quot;36&quot;,&quot;date&quot;:&quot;21/05/2015&quot;,&quot;id&quot;:&quot;2587&quot;}]</pre><h4>Map to Class with Index</h4><p>To map the data to a custom Person class, use a mapping function:</p><pre>var ret7 = Engine.ReadExcelFileByIndex(filePath, (i, rowData) =&gt;<br>{<br>    //to avoid first header raw<br>    if (i &gt; 0)<br>    {<br>        return new Person<br>        {<br>            Age = int.Parse(rowData[5]),<br>            Country = rowData[4],<br>            Date = DateTime.ParseExact(rowData[6], &quot;dd/MM/yyyy&quot;, CultureInfo.InvariantCulture),<br>            FirstName = rowData[1],<br>            Gender = rowData[3],<br>            Id = int.Parse(rowData[7]),<br>            LastName = rowData[2],<br>            Index = int.Parse(rowData[0]),<br>        };<br>    }<br>    return null;<br>});<br>Console.WriteLine(JsonSerializer.Serialize(ret7.Take(3)));</pre><p>Here’s the Person class:</p><pre>public class Person<br>{<br>    public int Index { get; set; }<br>    public string FirstName { get; set; } = &quot;&quot;;<br>    public string LastName { get; set; } = &quot;&quot;;<br>    public string Gender { get; set; } = &quot;&quot;;<br>    public string Country { get; set; } = &quot;&quot;;<br>    public int Age { get; set; }<br>    public DateTime Date { get; set; }<br>    public int Id { get; set; }<br>}</pre><pre>[{&quot;Index&quot;:1,&quot;FirstName&quot;:&quot;Dulce&quot;,&quot;LastName&quot;:&quot;Abril&quot;,&quot;Gender&quot;:&quot;Female&quot;,&quot;Country&quot;:&quot;United States&quot;,&quot;Age&quot;:32,&quot;Date&quot;:&quot;2017-10-15T00:00:00&quot;,&quot;Id&quot;:1562},<br>{&quot;Index&quot;:2,&quot;FirstName&quot;:&quot;Mara&quot;,&quot;LastName&quot;:&quot;Hashimoto&quot;,&quot;Gender&quot;:&quot;Female&quot;,&quot;Country&quot;:&quot;Great Britain&quot;,&quot;Age&quot;:25,&quot;Date&quot;:&quot;2016-08-16T00:00:00&quot;,&quot;Id&quot;:1582},<br>{&quot;Index&quot;:3,&quot;FirstName&quot;:&quot;Philip&quot;,&quot;LastName&quot;:&quot;Gent&quot;,&quot;Gender&quot;:&quot;Male&quot;,&quot;Country&quot;:&quot;France&quot;,&quot;Age&quot;:36,&quot;Date&quot;:&quot;2015-05-21T00:00:00&quot;,&quot;Id&quot;:2587}]</pre><h4>Map to Class with headers</h4><p>To map the data to a custom Person class, use a mapping function:</p><pre>var ret8 = Engine.ReadExcelFileByHeader(filePath, (rowData) =&gt;<br>{<br>    return new Person<br>    {<br>        Age = int.Parse(rowData[&quot;age&quot;]),<br>        Country = rowData[&quot;country&quot;],<br>        Date = DateTime.ParseExact(rowData[&quot;date&quot;], &quot;dd/MM/yyyy&quot;, CultureInfo.InvariantCulture),<br>        FirstName = rowData[&quot;firstname&quot;],<br>        Gender = rowData[&quot;gender&quot;],<br>        Id = int.Parse(rowData[&quot;id&quot;]),<br>        LastName = rowData[&quot;lastname&quot;],<br>        Index = int.Parse(rowData[&quot;index&quot;]),<br>    };<br>});<br>Console.WriteLine(JsonSerializer.Serialize(ret8.Take(3)));</pre><pre>[{&quot;Index&quot;:1,&quot;FirstName&quot;:&quot;Dulce&quot;,&quot;LastName&quot;:&quot;Abril&quot;,&quot;Gender&quot;:&quot;Female&quot;,&quot;Country&quot;:&quot;United States&quot;,&quot;Age&quot;:32,&quot;Date&quot;:&quot;2017-10-15T00:00:00&quot;,&quot;Id&quot;:1562},<br>{&quot;Index&quot;:2,&quot;FirstName&quot;:&quot;Mara&quot;,&quot;LastName&quot;:&quot;Hashimoto&quot;,&quot;Gender&quot;:&quot;Female&quot;,&quot;Country&quot;:&quot;Great Britain&quot;,&quot;Age&quot;:25,&quot;Date&quot;:&quot;2016-08-16T00:00:00&quot;,&quot;Id&quot;:1582},<br>{&quot;Index&quot;:3,&quot;FirstName&quot;:&quot;Philip&quot;,&quot;LastName&quot;:&quot;Gent&quot;,&quot;Gender&quot;:&quot;Male&quot;,&quot;Country&quot;:&quot;France&quot;,&quot;Age&quot;:36,&quot;Date&quot;:&quot;2015-05-21T00:00:00&quot;,&quot;Id&quot;:2587}]</pre><h4>Direct Cast</h4><p>Reading Excel File by Headers with Inbuilt Mapping Function</p><pre>var ret9 = Engine.ReadExcelFileByHeader&lt;Person&gt;(filePath);<br>Console.WriteLine(JsonSerializer.Serialize(ret9.Take(3)));</pre><pre>[{&quot;Index&quot;:1,&quot;FirstName&quot;:&quot;Dulce&quot;,&quot;LastName&quot;:&quot;Abril&quot;,&quot;Gender&quot;:&quot;Female&quot;,&quot;Country&quot;:&quot;United States&quot;,&quot;Age&quot;:32,&quot;Date&quot;:&quot;2017-10-15T00:00:00&quot;,&quot;Id&quot;:1562},<br>{&quot;Index&quot;:2,&quot;FirstName&quot;:&quot;Mara&quot;,&quot;LastName&quot;:&quot;Hashimoto&quot;,&quot;Gender&quot;:&quot;Female&quot;,&quot;Country&quot;:&quot;Great Britain&quot;,&quot;Age&quot;:25,&quot;Date&quot;:&quot;2016-08-16T00:00:00&quot;,&quot;Id&quot;:1582},<br>{&quot;Index&quot;:3,&quot;FirstName&quot;:&quot;Philip&quot;,&quot;LastName&quot;:&quot;Gent&quot;,&quot;Gender&quot;:&quot;Male&quot;,&quot;Country&quot;:&quot;France&quot;,&quot;Age&quot;:36,&quot;Date&quot;:&quot;2015-05-21T00:00:00&quot;,&quot;Id&quot;:2587}]</pre><h4>Conclusion</h4><p>ExcelShaper simplifies Excel data processing in C#. Its ease of use and flexibility make it an excellent choice for developers working with Excel files.</p><h4>Additional Resources</h4><ul><li><a href="https://github.com/andissanayake/ExcelShaper">GitHub Repository</a></li><li><a href="https://www.nuget.org/packages/ExcelShaper">NuGet Package</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eeea74f7571f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Modular Web Applications: A Guide to Microfrontend Architecture with Nx and Angular]]></title>
            <link>https://medium.com/@disa2aka/building-modular-web-applications-a-guide-to-microfrontend-architecture-with-nx-and-angular-a5f0b397dce8?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/a5f0b397dce8</guid>
            <category><![CDATA[micro-frontends]]></category>
            <category><![CDATA[monorepo]]></category>
            <category><![CDATA[nx]]></category>
            <category><![CDATA[modular]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Sat, 09 Mar 2024 04:43:15 GMT</pubDate>
            <atom:updated>2024-03-21T10:37:44.103Z</atom:updated>
            <content:encoded><![CDATA[<h3>Microfrontend Architecture with Nx and Angular</h3><p><strong>Microfrontend architecture</strong> is a design approach for developing web applications as a collection of smaller, independent frontend apps. This strategy enables teams to work in parallel on different features or components of a larger application, improving scalability, flexibility, and the speed of development. Microfrontends extend the concepts of microservices from the backend to the frontend, allowing different parts of a web application to be developed, tested, and deployed independently.</p><h3>What are Microfrontends?</h3><p>Microfrontends are essentially small, independent applications that work together to form a larger, cohesive whole. Each microfrontend is responsible for a distinct feature or domain of the application, such as a product search or user profile. These components can be developed using different frameworks or technologies, depending on the team’s expertise or the specific requirements of each component.</p><h3>Benefits of Microfrontend Architecture</h3><ol><li><strong>Decoupled Development</strong>: Teams can work on separate components independently, reducing dependencies and conflicts. This leads to faster development cycles and easier code management.</li><li><strong>Technology Agnosticism:</strong> Microfrontends allow the use of different technologies and frameworks within the same application, enabling the best tool for each specific task.</li><li><strong>Scalable Teams</strong>: The architecture supports scaling development teams by allowing multiple teams to work in parallel without stepping on each other’s toes.</li><li><strong>Easier Upgrades and Updates</strong>: Updating or upgrading a specific part of the application is easier and less risky, as changes are isolated to individual microfrontends.</li><li><strong>Reusable Components</strong>: Components developed for one part of the application can be reused elsewhere, promoting efficiency and consistency across the platform.</li></ol><h3>Challenges of Microfrontend Architecture</h3><ol><li><strong>Integration Complexity</strong>: Ensuring that all microfrontends work together seamlessly can be challenging, especially when different technologies are involved.</li><li><strong>Performance Overhead</strong>: The need to load multiple frameworks and libraries can impact the application’s overall performance and load times.</li><li><strong>Consistent User Experience</strong>: Maintaining a uniform look and feel across the application requires extra coordination and effort.</li><li><strong>Operational Complexity</strong>: Deploying and managing multiple microfrontends can be more complex than a monolithic application, requiring sophisticated CI/CD pipelines and monitoring solutions.</li></ol><h3>Let’s set up a sample App</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/792/1*vulvUCIxpdi0QxZCFOgIRw.png" /><figcaption>Diagram</figcaption></figure><ol><li>App Shell: This is the main container of your application. Think of it as the skeleton or the frame that holds everything together. The App Shell is responsible for loading the microfrontends (App-One and App-Two) and ensures that the user navigates smoothly between different parts of the application. It’s like the entryway that directs visitors to various rooms in a house.</li><li>App-One and App-Two: These are the microfrontends, akin to individual rooms within the house. Each room (microfrontend) serves a distinct purpose or feature of the application. For example, App-One might handle user profiles, while App-Two could be focused on product searches. They are independent of each other, meaning they can be updated, changed, or developed separately without affecting the rest of the house.</li><li>UI Library: This is a collection of reusable UI components, such as buttons, input fields, and labels, that both App-One and App-Two can use. It’s similar to having a set of furniture and decorations that can be used to furnish both rooms, ensuring they share a consistent look and feel. The UI Library helps maintain design consistency across your application and speeds up development by allowing you to reuse components.</li><li><strong>Install Nx CLI</strong></li></ol><pre>npm install -g nx</pre><p><strong>2</strong>. <strong>Create a New Nx Workspace</strong></p><pre>echo y | npx create-nx-workspace@18.0.7 app-workspace --preset=apps; cd app-workspace</pre><p>The command echo y | npx create-nx-workspace@18.0.7 app-workspace --preset=apps; cd app-workspace automates the creation of an Nx workspace named app-workspace with version 18.0.7, set up for application development (--preset=apps). It automatically agrees to any setup prompts (echo y |), and upon completion, navigates into the newly created workspace directory.</p><p><strong>3</strong>. <strong>Adding Angular support</strong></p><pre>npx nx add @nrwl/angular</pre><p>The command npx nx add @nrwl/angular adds Angular support to your Nx workspace, enabling Angular app development within that environment.</p><p><strong>4</strong>. <strong>Adding App Shell (App Shell is the base application)</strong></p><pre>npx nx generate @nrwl/angular:app apps/app-shell --style=scss --routing=true --e2eTestRunner=cypress --bundler=webpack --ssr=false</pre><p>The command npx nx generate @nrwl/angular:app apps/app-shell --style=scss --routing=true --e2eTestRunner=cypress --bundler=webpack --ssr=false creates a new Angular application named app-shell in an Nx workspace, with SCSS for styling, routing enabled, Cypress for end-to-end testing, using Webpack as the bundler, and without server-side rendering.</p><p><strong>5. Adding two microfrontends</strong></p><pre>npx nx generate @nrwl/angular:app apps/app-one --style=scss --routing=true --e2eTestRunner=cypress --bundler=webpack --ssr=false;npx nx generate @nrwl/angular:app apps/app-two --style=scss --routing=true --e2eTestRunner=cypress --bundler=webpack --ssr=false</pre><p><strong>6. Generating Library</strong></p><pre>npx nx generate @nrwl/angular:lib ui --directory=libs/ui;npx nx generate @nrwl/angular:component ui-button --directory=libs/ui/src/lib/ui-button --export;npx nx generate @nrwl/angular:component ui-label --directory=libs/ui/src/lib/ui-label --export</pre><p>In this command sequence, we use Nx to create a UI library within an Angular project, and then generate two components within that library: ui-button and ui-label. The commands organize these components into a structured directory (libs/ui) for easy management and ensure they&#39;re ready for use across the project by marking them as exported. This approach streamlines the development process by centralizing reusable UI elements, facilitating their reuse across different parts of the application, and maintaining consistency in design and functionality.</p><h3>All the microfrontends and libraries are ready let&#39;s integrate everything.</h3><ol><li><strong>Let’s add aliases</strong></li></ol><p>By adding aliases in tsconfig.base.json, we make it easier to work with our project. It&#39;s like assigning nicknames to frequently used folders so we don&#39;t have to type out long addresses every time we need something from them. This makes our code cleaner and our lives a bit easier, especially as our project grows.</p><p>To add aliases you have to edit tsconfig.base.json</p><pre>{<br>  &quot;compileOnSave&quot;: false,<br>  &quot;compilerOptions&quot;: {<br>    &quot;rootDir&quot;: &quot;.&quot;,<br>    &quot;sourceMap&quot;: true,<br>    &quot;declaration&quot;: false,<br>    &quot;moduleResolution&quot;: &quot;node&quot;,<br>    &quot;emitDecoratorMetadata&quot;: true,<br>    &quot;experimentalDecorators&quot;: true,<br>    &quot;importHelpers&quot;: true,<br>    &quot;target&quot;: &quot;es2015&quot;,<br>    &quot;module&quot;: &quot;esnext&quot;,<br>    &quot;lib&quot;: [&quot;es2020&quot;, &quot;dom&quot;],<br>    &quot;skipLibCheck&quot;: true,<br>    &quot;skipDefaultLibCheck&quot;: true,<br>    &quot;baseUrl&quot;: &quot;.&quot;,<br>    &quot;paths&quot;: {<br>      &quot;@app-workspace/app-one/*&quot;: [&quot;apps/app-one/src/app/*&quot;],<br>      &quot;@app-workspace/app-two/*&quot;: [&quot;apps/app-two/src/app/*&quot;],<br>      &quot;@app-workspace/ui&quot;: [&quot;libs/ui/src/index.ts&quot;]<br>    }<br>  },<br>  &quot;exclude&quot;: [&quot;node_modules&quot;, &quot;tmp&quot;]<br>}</pre><p>2. <strong>Updating routes</strong></p><p>To help users navigate to either App-One or App-Two, we need to adjust the app.routes.ts file within the App Shell, located at app-workspace\apps\app-shell\src\app. By setting up these routes, we create clear paths to each microfrontend, ensuring users can easily switch between them as needed.</p><pre>import { Route } from &#39;@angular/router&#39;;<br><br>export const appRoutes: Route[] = [<br>    {<br>        path: &#39;app-one&#39;,<br>        loadComponent: () =&gt; import(&#39;@app-workspace/app-one/app.component&#39;).then(m =&gt; m.AppComponent)<br>      },<br>      {<br>        path: &#39;app-two&#39;,<br>        loadComponent: () =&gt; import(&#39;@app-workspace/app-two/app.component&#39;).then(m =&gt; m.AppComponent)<br>      },<br>];</pre><p>3. <strong>Clean up</strong></p><p>For a bit of housekeeping, let’s remove the default nx-welcome.component and give our app components unique, descriptive names. This step, while optional, sharpens the identity of our components, making our project more intuitive. By doing so, your app components will look something like this</p><pre>&lt;h1&gt;App Shell&lt;/h1&gt;<br>&lt;router-outlet&gt;&lt;/router-outlet&gt;</pre><p>4. <strong>The last step is using the library.</strong></p><p>The final step in setting up our architecture involves integrating the shared UI library into our microfrontends. This means importing the library components we need, such as buttons or labels, directly into our microfrontend apps. By doing this, we ensure that all parts of our application can use these well-designed, consistent components, making our app look and feel unified across different sections. It’s like giving all rooms in a house access to the same set of quality furniture, ensuring a cohesive style throughout.</p><pre>import { Component } from &#39;@angular/core&#39;;<br>import { RouterModule } from &#39;@angular/router&#39;;<br>import {  UiLabelComponent } from &#39;@app-workspace/ui&#39;;<br>@Component({<br>  standalone: true,<br>  imports: [ RouterModule,UiLabelComponent],<br>  selector: &#39;app-workspace-root&#39;,<br>  templateUrl: &#39;./app.component.html&#39;,<br>  styleUrl: &#39;./app.component.scss&#39;,<br>})<br>export class AppComponent {<br>  title = &#39;app-one&#39;;<br>}</pre><p>Use the library</p><pre>&lt;h2&gt;App One&lt;/h2&gt;<br>&lt;app-workspace-ui-label&gt;&lt;/app-workspace-ui-label&gt;<br>&lt;router-outlet&gt;&lt;/router-outlet&gt;</pre><h3><strong>Relationship</strong></h3><p>To visualize how all parts of our project are connected, run the command npx nx graph. This generates a comprehensive graph showing the relationships between the apps and libraries in your Nx workspace. After running the command, click on the &quot;show all projects&quot; button to see the full picture. This visual aid is incredibly helpful for understanding the structure of your project and how each piece fits together, almost like laying out a map of a city to see all the neighbourhoods and how they&#39;re connected.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wjgCeNTCOj2FPO2v99iWzQ.png" /><figcaption>Relationships</figcaption></figure><h3>Run</h3><p>To get your project up and running, simply use the command npx nx serve app-shell. This fires up your main container, the App Shell, and serves your application so you can see it in action in a web browser. It’s like turning the key in the ignition to start your car—this command gets everything moving and lets you see how your app looks and behaves in real-time.</p><p>Navigate to <a href="http://localhost:4200/">http://localhost:4200/</a> , <a href="http://localhost:4200/app-one">http://localhost:4200/app-one</a>, and <a href="http://localhost:4200/app-two">http://localhost:4200/app-two</a> to see how navigation works between our microfrontends.</p><h3>Summary</h3><p>The example showcases setting up a microfrontend architecture with Nx and Angular, where an application is segmented into an app-shell, two microfrontends (app-one and app-two), and a shared UI library. This structure facilitates independent development and deployment of features, allows for the use of varied technologies across teams, and promotes reusability and consistency through a common library of UI components. The approach optimizes development processes, enhances scalability, and ensures a cohesive user experience across the application.</p><h3>Source Code</h3><p>For the complete code and more examples, check out the GitHub repository: <a href="https://github.com/andissanayake/Microfrontend">Microfrontend</a></p><h3>Acknowledgment</h3><p>A heartfelt thank you to <strong>Darshana Hettiarachchi</strong> for guiding me into the Nx world, and enriching my development experience.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a5f0b397dce8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ASP.NET 8 Token Authentication for Web API and React… (Part 3: React Frontend)]]></title>
            <link>https://medium.com/@disa2aka/token-authentication-integration-testing-asp-net-32bdab0f9967?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/32bdab0f9967</guid>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[axios]]></category>
            <category><![CDATA[webapi]]></category>
            <category><![CDATA[antd]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Sun, 03 Mar 2024 15:33:27 GMT</pubDate>
            <atom:updated>2024-03-23T03:36:47.992Z</atom:updated>
            <content:encoded><![CDATA[<h3>ASP.NET 8 Token Authentication for Web API and React with Integration Testing(Part 3: React Frontend)</h3><p>The final part of our series brings the user interface to life, integrating our secure ASP.NET 8 Web API with a React and Redux frontend. In this article, we’ll cover how to manage authentication states, securely store and handle authentication tokens, and communicate with the backend API from your React application. We’ll also delve into best practices for protecting routes and managing user sessions in a SPA (Single Page Application) context. By the end of this guide, you’ll have a fully functional, secure web application with a modern, reactive frontend that seamlessly interacts with your ASP.NET backend. This part is essential for developers looking to bridge the gap between backend security and frontend user experience.</p><p><a href="https://medium.com/@disa2aka/token-authentication-asp-net-1e7fdfb838bb"><strong>Part 1: Setting Up API</strong></a><strong><br></strong><a href="https://medium.com/@disa2aka/token-authentication-integration-testing-asp-net-1ff49b728faf"><strong>Part 2: Setting Up Integration Tests for API Project</strong></a><strong><br>Part 3: Setting Up React Client</strong></p><h3>Web Project: React Frontend</h3><p>The web project is created with <a href="https://vitejs.dev/">Vite</a>. We are using <a href="https://redux.js.org/redux-toolkit/overview">ReduxJS Toolkit</a> for state management, <a href="https://ant.design/">Ant Design (AntD)</a> for frontend design, and <a href="https://axios-http.com/docs/intro">Axios </a>for API call management.</p><p>Visual Studio 2022 provides a new JavaScript project type, and we are utilizing that for our project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HCZMWI8EfFKvlCY_Xu7ovg.png" /><figcaption>Javascript Project type for visual studio</figcaption></figure><p>Let’s go through the most important files to understand how the project is wired up.</p><h3>Web project files</h3><p>In our application, the entry point is <strong>main.tsx</strong>, where we configure fundamental aspects such as Redux state management, client-side routing using React Router, and an Axios API interceptor. This file initializes the React app, wraps it in StrictMode for enhanced development checks, and renders the main ‘App’ component within the specified ‘root’ element in the HTML document.</p><pre>import React from &quot;react&quot;;<br>import ReactDOM from &quot;react-dom/client&quot;;<br>import App from &quot;./App.tsx&quot;;<br>import { BrowserRouter } from &quot;react-router-dom&quot;;<br>import { Provider } from &quot;react-redux&quot;;<br>import { store } from &quot;./app/store.ts&quot;;<br>import { AxiosApiInterceptor } from &quot;./app/AxiosApiInterceptor.ts&quot;;<br><br>ReactDOM.createRoot(document.getElementById(&quot;root&quot;)!).render(<br>  &lt;React.StrictMode&gt;<br>    &lt;Provider store={store}&gt;<br>      &lt;BrowserRouter&gt;<br>        &lt;AxiosApiInterceptor /&gt;<br>        &lt;App /&gt;<br>      &lt;/BrowserRouter&gt;<br>    &lt;/Provider&gt;<br>  &lt;/React.StrictMode&gt;<br>);</pre><p><strong>store.ts </strong>sets up the Redux store configuration for a React application using the <strong>@reduxjs/toolkit</strong> library. It defines an authentication reducer (<strong>authReducer</strong>) and combines it with state persistence using <strong>redux-persist</strong>. The <strong>configureStore </strong>function creates the Redux store, enabling DevTools during development. The <strong>persistStore </strong>function is used to create a persisted version of the store, ensuring that the authentication state persists across page reloads. Additionally, type definitions (<strong>AppDispatch</strong>, <strong>RootState</strong>, and <strong>AppThunk</strong>) are provided for consistent use throughout the application. Overall, this configuration facilitates efficient state management, asynchronous actions, and persistent storage for the authentication slice of the Redux store.</p><pre>import {<br>  configureStore,<br>  ThunkAction,<br>  Action,<br>  combineReducers,<br>} from &quot;@reduxjs/toolkit&quot;;<br>import authReducer from &quot;../features/user/authSlice&quot;;<br>import storage from &quot;redux-persist/lib/storage&quot;; <br>import { persistReducer, persistStore } from &quot;redux-persist&quot;;<br>import thunk from &quot;redux-thunk&quot;;<br><br>const authPersistConfig = {<br>  key: &quot;auth&quot;,<br>  storage:storage,<br>};<br><br>const rootReducer = combineReducers({<br>  auth: persistReducer(authPersistConfig, authReducer),<br>});<br><br>export const store = configureStore({<br>  reducer: rootReducer,<br>  devTools: process.env.NODE_ENV !== &quot;production&quot;,<br>  middleware: [thunk],<br>});<br><br>export const persister = persistStore(store);<br><br>export type AppDispatch = typeof store.dispatch;<br>export type RootState = ReturnType&lt;typeof store.getState&gt;;<br>export type AppThunk&lt;ReturnType = void&gt; = ThunkAction&lt;<br>  ReturnType,<br>  RootState,<br>  unknown,<br>  Action&lt;string&gt;<br>&gt;;</pre><p><strong>authAPI.ts </strong>defines a set of functions for making API requests related to user authentication in a React application. Utilizing Axios, the <strong>login</strong>, <strong>refreshToken</strong>, <strong>register</strong>, <strong>logout</strong>, and <strong>profileApi </strong>functions interact with a backend server at a specified base URL. Each function handles its respective endpoint, such as user <strong>login</strong>, <strong>token refresh</strong>, <strong>registration</strong>, <strong>logout</strong>, and <strong>fetching user profile</strong>. In case of any errors during API requests, the code logs the exceptions to the console. This collection of functions demonstrates a straightforward and modular approach to integrating user authentication API calls in a React application.</p><pre>import axios from &quot;axios&quot;;<br>import { iAppResponse } from &quot;../../app/appResponse&quot;;<br><br>const BASE_URL = &quot;https://localhost:1002&quot;;<br><br>export const login = async (email: string, password: string) =&gt; {<br>  const response = await axios.post&lt;<br>  iAppResponse&lt;{ accessToken: string; refreshToken: string }&gt;<br>  &gt;(`${BASE_URL}/user/login`, {<br>    email: email,<br>    password: password,<br>  }).catch((ex)=&gt;{<br>    console.log(ex);<br>  });<br>  return response?.data;<br>};<br>export const refreshToken = async (data: {<br>  accessToken: string;<br>  refreshToken: string;<br>}) =&gt; {<br>  const response = await axios.post&lt;<br>  iAppResponse&lt;{ accessToken: string; refreshToken: string }&gt;<br>  &gt;(`${BASE_URL}/user/refreshToken`, data).catch((ex)=&gt;{<br>    console.log(ex);<br>  });;<br>  return response?.data;<br>};<br>export const register = async (email: string, password: string) =&gt; {<br>  const response = await axios.post&lt;iAppResponse&lt;{}&gt;&gt;(<br>    `${BASE_URL}/user/register`,<br>    {<br>      email: email,<br>      password: password,<br>    }<br>  ).catch((ex)=&gt;{<br>    console.log(ex);<br>  });<br>  return response?.data;<br>};<br>export const logout = async () =&gt; {<br>  const response = await axios.post&lt;iAppResponse&lt;boolean&gt;&gt;(<br>    `${BASE_URL}/user/logout`<br>  ).catch((ex)=&gt;{<br>    console.log(ex);<br>  });;<br>  return response?.data;<br>};<br>export const profileApi = async () =&gt; {<br>  const response = await axios.post(`${BASE_URL}/user/profile`).catch((ex)=&gt;{<br>    console.log(ex);<br>  });<br>  return response?.data;<br>};</pre><p><strong>authSlice.ts </strong>for managing authentication-related state in the application. It includes a set of reducers to update or reset authentication tokens, manage loading states, and handle logout asynchronously. Overall, this slice facilitates the centralized management of authentication-related data, making it easier to handle user authentication and related asynchronous actions within the Redux store.</p><pre>import { PayloadAction, createAsyncThunk, createSlice } from &quot;@reduxjs/toolkit&quot;;<br>import { RootState } from &quot;../../app/store&quot;;<br>import { jwtDecode } from &quot;jwt-decode&quot;;<br>import { logout } from &quot;./authAPI&quot;;<br><br>export interface iUser {<br>  Id: string;<br>  RoleClaim: Array&lt;string&gt;;<br>  UserName: string;<br>}<br>export interface iAuthState {<br>  status: &quot;idle&quot; | &quot;loading&quot; | &quot;failed&quot;;<br>  accessToken?: string;<br>  refreshToken?: string;<br>  user?: iUser;<br>}<br><br>const initialState: iAuthState = {<br>  status: &quot;idle&quot;,<br>};<br>export const logoutAsync = createAsyncThunk(&quot;user/logout&quot;, async () =&gt; {<br>  const response = await logout();<br>  // The value we return becomes the `fulfilled` action payload<br>  return response?.data;<br>});<br>export const authSlice = createSlice({<br>  name: &quot;auth&quot;,<br>  initialState,<br>  // The `reducers` field lets us define reducers and generate associated actions<br>  reducers: {<br>    updateToken: (<br>      state,<br>      action: PayloadAction&lt;{ accessToken: string; refreshToken: string }&gt;<br>    ) =&gt; {<br>      state.accessToken = action.payload.accessToken;<br>      state.refreshToken = action.payload.refreshToken;<br>      state.user = jwtDecode&lt;iUser&gt;(action.payload.accessToken);<br>    },<br>    resetToken: (state) =&gt; {<br>      state.accessToken = undefined;<br>      state.refreshToken = undefined;<br>      state.user = undefined;<br>    },<br>    setLoading: (state) =&gt; {<br>      state.status = &quot;loading&quot;;<br>    },<br>    resetLoading: (state) =&gt; {<br>      state.status = &quot;idle&quot;;<br>    },<br>  },<br>  extraReducers: (builder) =&gt; {<br>    builder<br>      .addCase(logoutAsync.pending, (state) =&gt; {<br>        state.status = &quot;loading&quot;;<br>      })<br>      .addCase(logoutAsync.fulfilled, (state) =&gt; {<br>        state.status = &quot;idle&quot;;<br>        state.accessToken = undefined;<br>        state.refreshToken = undefined;<br>        state.user = undefined;<br>      })<br>      .addCase(logoutAsync.rejected, (state) =&gt; {<br>        state.status = &quot;failed&quot;;<br>      });<br>  },<br>});<br><br>export const { updateToken, resetToken, setLoading, resetLoading } =<br>  authSlice.actions;<br>export const selectAuth = (state: RootState) =&gt; state.auth;<br>export default authSlice.reducer;</pre><p><strong>AxiosApiInterceptor.ts </strong>component is a crucial part of the React application, utilizing Axios interceptors to manage authentication tokens. When a request is made, it ensures the inclusion of the access token in the Authorization header, improving security. Additionally, it monitors responses, particularly handling cases where a 401 status code indicates an expired token. In such situations, it attempts to refresh the token using a provided function. If the refresh is successful, the Redux store is updated with the new tokens, allowing the original request to be retried. If the refresh fails, or if no refresh token is available, the authentication state is reset. This component helps maintain a seamless and secure user authentication experience throughout the application.</p><pre>import { useEffect } from &quot;react&quot;;<br>import axios from &quot;axios&quot;;<br>import {<br>  resetToken,<br>  selectAuth,<br>  updateToken,<br>} from &quot;../features/user/authSlice&quot;;<br>import { useAppDispatch, useAppSelector } from &quot;./hooks&quot;;<br>import { refreshToken } from &quot;../features/user/authAPI&quot;;<br><br>export const AxiosApiInterceptor = () =&gt; {<br>  const authData = useAppSelector(selectAuth);<br>  const dispatch = useAppDispatch();<br>  useEffect(() =&gt; {<br>    const requestInterceptor = axios.interceptors.request.use(<br>      async (config) =&gt; {<br>        const accessToken = authData.accessToken;<br>        if (accessToken &amp;&amp; !config.headers.Authorization) {<br>          config.headers.Authorization = `Bearer ${accessToken}`;<br>        }<br>        return config;<br>      }<br>    );<br><br>    // Response interceptor<br>    const responseInterceptor = axios.interceptors.response.use(<br>      (response) =&gt; response,<br>      async (error) =&gt; {<br>        if (error.response &amp;&amp; error.response.status === 401) {<br>          // Token expired, attempt to refresh it<br>          if (authData.refreshToken &amp;&amp; authData.accessToken) {<br>            // Make a refresh token request and update access token<br>            try {<br>              const response = await refreshToken({<br>                accessToken: authData.accessToken,<br>                refreshToken: authData.refreshToken,<br>              });<br>              if (response &amp;&amp; response.isSucceed &amp;&amp; response.data) {<br>                dispatch(updateToken(response.data));<br>                error.config.headers.Authorization = `Bearer ${response.data.accessToken}`;<br>                return axios.request(error.config);<br>              } else {<br>                dispatch(resetToken());<br>              }<br>            } catch (refreshError) {<br>              dispatch(resetToken());<br>              throw refreshError;<br>            }<br>          } else {<br>            dispatch(resetToken());<br>          }<br>        }<br><br>        return Promise.reject(error);<br>      }<br>    );<br><br>    return () =&gt; {<br>      // Cleanup: Remove the interceptors when the component unmounts<br>      axios.interceptors.request.eject(requestInterceptor);<br>      axios.interceptors.response.eject(responseInterceptor);<br>    };<br>  }, [authData, dispatch]);<br><br>  return null; // This component doesn&#39;t render anything<br>};</pre><p>The remaining components are straightforward, and I trust you’ll find them self-explanatory. If you have any further inquiries or need clarification, please feel free to ask.</p><h3>Summary</h3><p>This code module serves as a comprehensive guide to integrating a React frontend with an authentication API. Focused on practical implementation, it employs Axios for API requests and demonstrates key functionalities such as user login, token refresh, registration, logout, and fetching user profile. The code promotes a modular and error-handling approach, logging exceptions to the console during API requests. This collection of functions showcases a robust integration of user authentication features within a React application, enhancing practicality and reliability.</p><h3>Source Code</h3><p>For the complete code and more examples, check out the GitHub repository: <a href="https://github.com/andissanayake/UnifiedApp">UnifiedApp</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=32bdab0f9967" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Effortless Authentication with Docker: Deploying Keycloak and PostgreSQL]]></title>
            <link>https://medium.com/@disa2aka/docker-deployments-for-keycloak-and-postgresql-e75707b155e5?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/e75707b155e5</guid>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[posgresql]]></category>
            <category><![CDATA[keycloak]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[sso-single-sign-on-portal]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Sat, 17 Feb 2024 16:12:17 GMT</pubDate>
            <atom:updated>2024-03-09T17:27:49.921Z</atom:updated>
            <content:encoded><![CDATA[<h3>Docker Compose Deployments for Keycloak and PostgreSQL</h3><h3>What is Keycloak</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/797/1*mOjpmOxisTJJeyvBmrBgmQ.png" /><figcaption>Keycloak</figcaption></figure><h4>Centralized User Management</h4><p>Keycloak allows you to centralize user management in one place, making it easier to manage users, roles, and permissions across multiple applications and services. It supports user federation, which means you can integrate it with existing user directories, such as LDAP or Active Directory.</p><h4>Single Sign-On (SSO) and Single Log-Out</h4><p>One of the most significant features of Keycloak is its support for Single Sign-On (SSO). SSO enables users to log in once and gain access to multiple applications without being prompted to log in again at each of them. Similarly, Single Log-Out allows users to log out from all applications simultaneously.</p><h4>Social Login</h4><p>Keycloak supports social login, allowing users to sign in using their social media accounts such as Google, Facebook, Twitter, etc. This feature enhances the user experience by simplifying the registration and login processes.</p><h4>Multi-factor Authentication (MFA)</h4><p>For enhanced security, Keycloak supports Multi-factor Authentication (MFA). This adds an extra layer of security by requiring users to provide two or more verification factors to gain access to an application.</p><h4>OpenID Connect (OIDC) and SAML</h4><p>Keycloak implements modern protocols like OpenID Connect (OIDC) and SAML 2.0 for authentication and authorization, making it versatile and compatible with a wide range of applications.</p><h4>Customizable Themes</h4><p>The look and feel of the login pages served by Keycloak can be customized according to your branding requirements. Keycloak allows for theme customizations where you can change the appearance of the login, registration, and account management pages.</p><h4>Administration Console</h4><p>Keycloak comes with an easy-to-use web-based administration console for managing realms, users, roles, and permissions. A realm in Keycloak is a space where you manage your users, credentials, roles, and groups.</p><h4>Security</h4><p>Keycloak provides robust security features out of the box, including SSL/TLS, password policies, brute force detection, and more. It also allows for the secure storage of user credentials.</p><h4>API Access Management</h4><p>Keycloak allows for securing application APIs by using tokens (JWT tokens or SAML assertions). It makes it easy to define which resources are secured and which roles or clients have access to them.</p><h4>Scalability and High Availability</h4><p>Keycloak is designed to be scalable and can be deployed in a high-availability configuration to ensure that authentication services are always available to users and applications.</p><h3>Setting up with docker</h3><p><em>docker-compose.yml file</em></p><pre>version: &#39;3.7&#39;<br><br>services:<br>  postgres:<br>    image: postgres:16.2<br>    volumes:<br>      - postgres_data:/var/lib/postgresql/data<br>    environment:<br>      POSTGRES_DB: ${POSTGRES_DB}<br>      POSTGRES_USER: ${POSTGRES_USER}<br>      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}<br>    networks:<br>      - keycloak_network<br><br>  keycloak:<br>    image: quay.io/keycloak/keycloak:23.0.6<br>    command: start<br>    environment:<br>      KC_HOSTNAME: localhost<br>      KC_HOSTNAME_PORT: 8080<br>      KC_HOSTNAME_STRICT_BACKCHANNEL: false<br>      KC_HTTP_ENABLED: true<br>      KC_HOSTNAME_STRICT_HTTPS: false<br>      KC_HEALTH_ENABLED: true<br>      KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}<br>      KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}<br>      KC_DB: postgres<br>      KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB}<br>      KC_DB_USERNAME: ${POSTGRES_USER}<br>      KC_DB_PASSWORD: ${POSTGRES_PASSWORD}<br>    ports:<br>      - 8080:8080<br>    restart: always<br>    depends_on:<br>      - postgres<br>    networks:<br>      - keycloak_network<br><br>volumes:<br>  postgres_data:<br>    driver: local<br><br>networks:<br>  keycloak_network:<br>    driver: bridge</pre><p><em>.env file</em></p><pre>POSTGRES_DB=keycloak_db<br>POSTGRES_USER=keycloak_db_user<br>POSTGRES_PASSWORD=keycloak_db_user_password<br>KEYCLOAK_ADMIN=admin<br>KEYCLOAK_ADMIN_PASSWORD=password</pre><h3>Services</h3><p><strong>PostgreSQL Service (postgres):</strong></p><ul><li>Image: Uses the Docker image postgres:16.2 to run a PostgreSQL server.</li><li>Volumes: Maps a volume named postgres_data tob/var/lib/postgresql/data inside the container for persistent storage of the database data.</li><li>Environment Variables: Configures the database with the name (POSTGRES_DB), user (POSTGRES_USER), and password (POSTGRES_PASSWORD) specified by the environment variables.</li><li>Networks: Connects to a custom network keycloak_network for communication with the Keycloak service.</li></ul><p><strong>Keycloak Service (keycloak):</strong></p><ul><li>Image: Utilizes quay.io/keycloak/keycloak:23.0.6 for running a Keycloak server.</li><li>Command: Specifies start as the command to run Keycloak.</li><li>Environment Variables: Sets up various settings including hostname (KC_HOSTNAME), port (KC_HOSTNAME_PORT), HTTP configuration (KC_HTTP_ENABLED, KC_HOSTNAME_STRICT_HTTPS), health check (KC_HEALTH_ENABLED), admin credentials (KEYCLOAK_ADMIN, KEYCLOAK_ADMIN_PASSWORD), and database connection details (KC_DB, KC_DB_URL, KC_DB_USERNAME, KC_DB_PASSWORD).</li><li>Ports: Exposes port 8080 on the host, mapping it to port 8080 on the Keycloak container for web access.</li><li>Restart Policy: Configured to always restart unless manually stopped.</li><li>Dependency: Declares a dependency on the Postgres service, ensuring it starts first.</li><li>Networks: Also connected to the keycloak_network.</li></ul><h3>Volumes</h3><p><strong>postgres_data</strong>: A named volume for storing PostgreSQL data persistently across container restarts, using the default local storage driver.</p><h3>Networks</h3><p><strong>keycloak_network</strong>: A custom network using the bridge driver, facilitating communication between the Keycloak and PostgreSQL containers.</p><h3>Environment Variables</h3><p>Specifies the values for database configuration (POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD) and Keycloak admin credentials (KEYCLOAK_ADMIN, KEYCLOAK_ADMIN_PASSWORD), which are essential for the services to communicate and operate securely.</p><h3>Summary</h3><p>This Docker Compose file sets up Keycloak and PostgreSQL, providing an authentication system with data storage. It specifies service configurations, including environment variables for access and a custom network for inter-service communication. Ideal for development, this setup lays the groundwork for application authentication management.</p><h3>Source Code</h3><p>For the complete code and more examples, check out the GitHub repository: <a href="https://github.com/andissanayake/ForgeContainer/tree/example/keycloak-docker-compose">ForgeContainer</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e75707b155e5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ASP.NET 8 Token Authentication for Web API and React… (Part 2: Integration Test)]]></title>
            <link>https://medium.com/@disa2aka/token-authentication-integration-testing-asp-net-1ff49b728faf?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/1ff49b728faf</guid>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[webapi]]></category>
            <category><![CDATA[token-authentication]]></category>
            <category><![CDATA[integration-testing]]></category>
            <category><![CDATA[xunit]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Fri, 02 Feb 2024 09:06:23 GMT</pubDate>
            <atom:updated>2024-03-23T03:35:32.589Z</atom:updated>
            <content:encoded><![CDATA[<h3>ASP.NET 8 Token Authentication for Web API and React with Integration Testing (Part 2: Integration Test)</h3><p>In the second part of our series, the focus shifts towards validating the security and reliability of our ASP.NET 8 Web API through comprehensive integration testing. Integration testing plays a critical role in ensuring that our authentication mechanisms work as intended, under various scenarios and edge cases. We’ll guide you through setting up your testing environment, writing test cases that cover a wide range of authentication scenarios, and interpreting the results to refine your authentication system. This article is designed to provide you with a deep understanding of how to effectively test web APIs, ensuring that your application remains secure and functions correctly under all circumstances.</p><p><a href="https://medium.com/@disa2aka/token-authentication-asp-net-1e7fdfb838bb"><strong>Part 1: Setting Up API</strong></a><strong><br>Part 2: Setting Up Integration Tests for API Project<br></strong><a href="https://medium.com/@disa2aka/token-authentication-integration-testing-asp-net-32bdab0f9967"><strong>Part 3: Setting Up React Client</strong></a></p><h3><strong>ApiTest </strong>Project: Integration test project for API.</h3><p>The ApiTest project is configured with xUnit, Microsoft.AspNetCore.Mvc.Testing, and FluentAssertions, are here to ensure our API runs smoothly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W0EXvmAq2xz1fTMt1bRIpA.png" /><figcaption>Integration Tests</figcaption></figure><h3>ApiTest Project Classes</h3><p><strong>CustomWebApplicationFactory, </strong>The purpose of setting up a custom environment for integration testing. It’s using an in-memory database to isolate the tests, and it’s removing and replacing services appropriately.</p><pre>    public class CustomWebApplicationFactory&lt;TProgram&gt;<br>        : WebApplicationFactory&lt;TProgram&gt; where TProgram : class<br>    {<br><br>        protected override void ConfigureWebHost(IWebHostBuilder builder)<br>        {<br>            builder.UseEnvironment(&quot;Test&quot;);<br>            builder.ConfigureServices(services =&gt;<br>            {<br>                var context = services.FirstOrDefault(descriptor =&gt; descriptor.ServiceType == typeof(ApplicationDbContext));<br>                if (context != null)<br>                {<br>                    services.Remove(context);<br>                    var options = services.Where(r =&gt; r.ServiceType == typeof(DbContextOptions)<br>                      || r.ServiceType.IsGenericType &amp;&amp; r.ServiceType.GetGenericTypeDefinition() == typeof(DbContextOptions&lt;&gt;)).ToArray();<br>                    foreach (var option in options)<br>                    {<br>                        services.Remove(option);<br>                    }<br>                }<br><br>                // Add a new registration for ApplicationDbContext with an in-memory database<br>                services.AddDbContext&lt;ApplicationDbContext&gt;(options =&gt;<br>                {<br>                    // Provide a unique name for your in-memory database<br>                    options.UseInMemoryDatabase(&quot;InMemoryDatabaseNameX&quot;);<br>                });<br>            });<br>        }<br>    }</pre><p>Settings up the environment as “Test”</p><pre>builder.UseEnvironment(&quot;Test&quot;);</pre><p>It’s going to load appsettings.test.json</p><pre>{<br>  &quot;TokenSettings&quot;: {<br>    &quot;Issuer&quot;: &quot;TestApi&quot;,<br>    &quot;Audience&quot;: &quot;TestApp&quot;,<br>    &quot;SecretKey&quot;: &quot;DFDGERsjsfjepoeoe@@#$$@$@123112sdaaadasQEWw&quot;,<br>    &quot;TokenExpireSeconds&quot;: 10,<br>    &quot;RefreshTokenExpireSeconds&quot;: 20<br>  },<br>  &quot;Logging&quot;: {<br>    &quot;LogLevel&quot;: {<br>      &quot;Default&quot;: &quot;Information&quot;,<br>      &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;<br>    }<br>  }<br>}</pre><p>This code block is preparing the environment for integration tests by ensuring a clean setup for the <strong>ApplicationDbContext </strong>service, removing any existing registrations, and then configuring it to use an in-memory database with a unique name.</p><pre>                var context = services.FirstOrDefault(descriptor =&gt; descriptor.ServiceType == typeof(ApplicationDbContext));<br>                if (context != null)<br>                {<br>                    services.Remove(context);<br>                    var options = services.Where(r =&gt; r.ServiceType == typeof(DbContextOptions)<br>                      || r.ServiceType.IsGenericType &amp;&amp; r.ServiceType.GetGenericTypeDefinition() == typeof(DbContextOptions&lt;&gt;)).ToArray();<br>                    foreach (var option in options)<br>                    {<br>                        services.Remove(option);<br>                    }<br>                }<br><br>                // Add a new registration for ApplicationDbContext with an in-memory database<br>                services.AddDbContext&lt;ApplicationDbContext&gt;(options =&gt;<br>                {<br>                    // Provide a unique name for your in-memory database<br>                    options.UseInMemoryDatabase(&quot;InMemoryDatabaseNameX&quot;);<br>                });</pre><p>Perfect! With the groundwork laid, let’s dive into the actual testing phase.</p><p><strong>UserEndPointTest </strong>covers different scenarios related to user authentication and registration, checking the API’s behavior under various conditions.</p><pre>public class UserEndPointTest(CustomWebApplicationFactory&lt;Program&gt; factory) : IClassFixture&lt;CustomWebApplicationFactory&lt;Program&gt;&gt;<br>{<br>    private readonly HttpClient _client = factory.CreateClient();<br><br>    [Fact]<br>    public async void LoginSuccessTest()<br>    {<br>        var userLoginRequest = new UserLoginRequest<br>        {<br>            Email = &quot;UnifiedAppAdmin&quot;,<br>            Password = &quot;UnifiedAppAdmin1!&quot;<br>        };<br><br>        var jsonContent = JsonSerializer.Serialize(userLoginRequest);<br>        var content = new StringContent(jsonContent, Encoding.UTF8, &quot;application/json&quot;);<br>        var response = await _client.PostAsync(&quot;/User/Login&quot;, content);<br>        if (response.IsSuccessStatusCode)<br>        {<br>            response.Should().NotBeNull();<br>            var userLoginResponse = await response.Content.ReadFromJsonAsync&lt;AppResponse&lt;UserLoginResponse&gt;&gt;();<br>            userLoginResponse.Should().NotBeNull();<br>            userLoginResponse?.IsSucceed.Should().BeTrue();<br>            userLoginResponse?.Data.Should().NotBeNull();<br>            userLoginResponse?.Data?.AccessToken.Should().NotBeNull();<br>            userLoginResponse?.Data?.RefreshToken.Should().NotBeNull();<br>        }<br>        else<br>        {<br>            Assert.Fail(&quot;Api call failed.&quot;);<br>        }<br>    }<br><br>    [Fact]<br>    public async void LoginFailTest()<br>    {<br>        var userLoginRequest = new UserLoginRequest<br>        {<br>            Email = &quot;UnifiedAppAdmin&quot;,<br>            Password = &quot;wrong_password&quot;<br>        };<br><br>        var jsonContent = JsonSerializer.Serialize(userLoginRequest);<br>        var content = new StringContent(jsonContent, Encoding.UTF8, &quot;application/json&quot;);<br>        var response = await _client.PostAsync(&quot;/User/Login&quot;, content);<br>        if (response.IsSuccessStatusCode)<br>        {<br>            response.Should().NotBeNull();<br>            var userLoginResponse = await response.Content.ReadFromJsonAsync&lt;AppResponse&lt;UserLoginResponse&gt;&gt;();<br>            userLoginResponse.Should().NotBeNull();<br>            userLoginResponse?.IsSucceed.Should().BeFalse();<br>            userLoginResponse?.Data.Should().BeNull();<br><br>        }<br>        else<br>        {<br>            Assert.Fail(&quot;Api call failed.&quot;);<br>        }<br>    }<br><br>    [Fact]<br>    public async void RegistrationTest()<br>    {<br>        var userLoginRequest = new UserRegisterRequest<br>        {<br>            Email = &quot;UnifiedAppAdmin1&quot;,<br>            Password = &quot;Pass@#123&quot;<br>        };<br><br>        var jsonContent = JsonSerializer.Serialize(userLoginRequest);<br>        var content = new StringContent(jsonContent, Encoding.UTF8, &quot;application/json&quot;);<br>        var response = await _client.PostAsync(&quot;/User/Register&quot;, content);<br>        if (response.IsSuccessStatusCode)<br>        {<br>            response.Should().NotBeNull();<br>            var userRegisterResponse = await response.Content.ReadFromJsonAsync&lt;AppResponse&lt;bool&gt;&gt;();<br>            userRegisterResponse.Should().NotBeNull();<br>            userRegisterResponse?.IsSucceed.Should().BeTrue();<br>            userRegisterResponse?.Data.Should().BeTrue();<br><br><br>        }<br>        else<br>        {<br>            Assert.Fail(&quot;Api call failed.&quot;);<br>        }<br>    }<br><br>    [Fact]<br>    public async void RegistrationExistingUserTest()<br>    {<br>        var userLoginRequest = new UserRegisterRequest<br>        {<br>            Email = &quot;UnifiedAppAdmin&quot;,<br>            Password = &quot;Pass@#123&quot;<br>        };<br><br>        var jsonContent = JsonSerializer.Serialize(userLoginRequest);<br>        var content = new StringContent(jsonContent, Encoding.UTF8, &quot;application/json&quot;);<br>        var response = await _client.PostAsync(&quot;/User/Register&quot;, content);<br>        if (response.IsSuccessStatusCode)<br>        {<br>            response.Should().NotBeNull();<br>            var userRegisterResponse = await response.Content.ReadFromJsonAsync&lt;AppResponse&lt;bool&gt;&gt;();<br>            userRegisterResponse.Should().NotBeNull();<br>            userRegisterResponse?.IsSucceed.Should().BeFalse();<br>            userRegisterResponse?.Data.Should().BeFalse();<br>            userRegisterResponse?.Messages.Any(m =&gt; m.Key == &quot;DuplicateUserName&quot;).Should().BeTrue();<br>        }<br>        else<br>        {<br>            Assert.Fail(&quot;Api call failed.&quot;);<br>        }<br>    }<br><br>    [Fact]<br>    public async void RegistrationPasswordTest()<br>    {<br>        var userLoginRequest = new UserRegisterRequest<br>        {<br>            Email = &quot;UnifiedAppAdmin&quot;,<br>            Password = &quot;123&quot;<br>        };<br><br>        var jsonContent = JsonSerializer.Serialize(userLoginRequest);<br>        var content = new StringContent(jsonContent, Encoding.UTF8, &quot;application/json&quot;);<br>        var response = await _client.PostAsync(&quot;/User/Register&quot;, content);<br>        if (response.IsSuccessStatusCode)<br>        {<br>            response.Should().NotBeNull();<br>            var userRegisterResponse = await response.Content.ReadFromJsonAsync&lt;AppResponse&lt;bool&gt;&gt;();<br>            userRegisterResponse.Should().NotBeNull();<br>            userRegisterResponse?.IsSucceed.Should().BeFalse();<br>            userRegisterResponse?.Data.Should().BeFalse();<br>            userRegisterResponse?.Messages.Any(m =&gt; m.Key == &quot;PasswordRequiresLower&quot;).Should().BeTrue();<br>            userRegisterResponse?.Messages.Any(m =&gt; m.Key == &quot;PasswordTooShort&quot;).Should().BeTrue();<br>            userRegisterResponse?.Messages.Any(m =&gt; m.Key == &quot;PasswordRequiresNonAlphanumeric&quot;).Should().BeTrue();<br>            userRegisterResponse?.Messages.Any(m =&gt; m.Key == &quot;PasswordRequiresUpper&quot;).Should().BeTrue();<br>        }<br>        else<br>        {<br>            Assert.Fail(&quot;Api call failed.&quot;);<br>        }<br>    }<br>}</pre><ol><li><strong>LoginSuccessTest</strong>:</li></ol><ul><li><em>Scenario:</em> Attempts to log in with correct credentials.</li><li><em>Expected Outcome:</em> Expect a successful response with a valid access token and refresh token.</li></ul><p><strong>2. LoginFailTest</strong>:</p><ul><li><em>Scenario:</em> Attempts to log in with incorrect credentials.</li><li><em>Expected Outcome:</em> Expect an unsuccessful response with no access token and a failed login status.</li></ul><p>3. <strong>RegistrationTest</strong>:</p><ul><li><em>Scenario:</em> Attempts to register a new user.</li><li><em>Expected Outcome:</em> Expect a successful response indicating a successful registration.</li></ul><p>4. <strong>RegistrationExistingUserTest</strong>:</p><ul><li><em>Scenario:</em> Attempts to register an existing user.</li><li><em>Expected Outcome:</em> Expect an unsuccessful response indicating a failed registration due to an existing user.</li></ul><p>5. <strong>RegistrationPasswordTest</strong>:</p><ul><li><em>Scenario:</em> Attempts to register a user with a weak password.</li><li><em>Expected Outcome:</em> Expect an unsuccessful response with specific error messages indicating password strength requirements.</li></ul><p><strong>TokenTest </strong>class contains a set of tests that cover different scenarios related to token-based authentication and refresh token functionality.</p><pre>public class TokenTest(CustomWebApplicationFactory&lt;Program&gt; factory) : IClassFixture&lt;CustomWebApplicationFactory&lt;Program&gt;&gt;<br>{<br>    private readonly HttpClient _client = factory.CreateClient();<br><br>    [Fact]<br>    public async void ApiSuccessTest()<br>    {<br>        var token = await GetToken();<br>        var apiRes = await GetSecureApiRes(token.AccessToken);<br>        apiRes.Should().NotBeNull();<br>        apiRes.StatusCode.Should().Be(HttpStatusCode.OK);<br><br>    }<br><br>    [Fact]<br>    public async void ApiFailureTest()<br>    {<br>        var apiRes = await GetSecureApiRes(&quot;&quot;);<br>        apiRes.Should().NotBeNull();<br>        apiRes.StatusCode.Should().Be(HttpStatusCode.Unauthorized);<br>    }<br><br>    [Fact]<br>    public async void TokenExpireTest()<br>    {<br>        var token = await GetToken();<br><br>        await Task.Delay(12000);<br>        HttpRequestMessage request = new(HttpMethod.Post, &quot;/User/Profile&quot;);<br>        request.Headers.Add(&quot;Authorization&quot;, &quot;Bearer &quot; + token.AccessToken);<br>        var apiRes = await _client.SendAsync(request);<br>        apiRes.Should().NotBeNull();<br>        apiRes.StatusCode.Should().Be(HttpStatusCode.Unauthorized);<br>    }<br><br>    [Fact]<br>    public async void GetRefreshTokenTest()<br>    {<br>        var token = await GetToken();<br>        await Task.Delay(12000);<br>        var rtRes = await GetRefreshToken(new UserRefreshTokenRequest<br>        {<br>            AccessToken = token.AccessToken,<br>            RefreshToken = token.RefreshToken<br>        });<br>        rtRes.Should().NotBeNull();<br>        rtRes.StatusCode.Should().Be(HttpStatusCode.OK);<br>        var userRefreshTokenResponse = await rtRes.Content.ReadFromJsonAsync&lt;AppResponse&lt;UserRefreshTokenResponse&gt;&gt;();<br>        userRefreshTokenResponse.Should().NotBeNull();<br>        userRefreshTokenResponse?.IsSucceed.Should().BeTrue();<br>        userRefreshTokenResponse?.Data?.Should().NotBeNull();<br>        var apiRes = await GetSecureApiRes(userRefreshTokenResponse?.Data?.AccessToken ?? &quot;&quot;);<br>        apiRes.Should().NotBeNull();<br>        apiRes.StatusCode.Should().Be(HttpStatusCode.OK);<br><br>    }<br><br>    [Fact]<br>    public async void RefreshTokenExpireTest()<br>    {<br>        var token = await GetToken();<br>        await Task.Delay(22000);<br>        var rtRes = await GetRefreshToken(new UserRefreshTokenRequest<br>        {<br>            AccessToken = token.AccessToken,<br>            RefreshToken = token.RefreshToken<br>        });<br>        rtRes.Should().NotBeNull();<br>        rtRes.StatusCode.Should().Be(HttpStatusCode.OK);<br>        var userRefreshTokenResponse = await rtRes.Content.ReadFromJsonAsync&lt;AppResponse&lt;UserRefreshTokenResponse&gt;&gt;();<br>        userRefreshTokenResponse.Should().NotBeNull();<br>        userRefreshTokenResponse?.IsSucceed.Should().BeFalse();<br>        userRefreshTokenResponse?.Data?.Should().BeNull();<br>        userRefreshTokenResponse?.Messages.Any(m =&gt; m.Key == &quot;token&quot;).Should().BeTrue();<br><br>    }<br><br>    private async Task&lt;UserLoginResponse&gt; GetToken()<br>    {<br>        var userLoginRequest = new UserLoginRequest<br>        {<br>            Email = &quot;UnifiedAppAdmin&quot;,<br>            Password = &quot;UnifiedAppAdmin1!&quot;<br>        };<br><br>        var jsonContent = JsonSerializer.Serialize(userLoginRequest);<br>        var content = new StringContent(jsonContent, Encoding.UTF8, &quot;application/json&quot;);<br>        var response = await _client.PostAsync(&quot;/User/Login&quot;, content);<br>        var userLoginResponse = await response.Content.ReadFromJsonAsync&lt;AppResponse&lt;UserLoginResponse&gt;&gt;();<br>        return userLoginResponse?.Data ?? default!;<br>    }<br>    private async Task&lt;HttpResponseMessage&gt; GetSecureApiRes(string accessToken)<br>    {<br>        HttpRequestMessage request = new(HttpMethod.Post, &quot;/User/Profile&quot;);<br>        request.Headers.Add(&quot;Authorization&quot;, &quot;Bearer &quot; + accessToken);<br>        return await _client.SendAsync(request);<br>    }<br>    private async Task&lt;HttpResponseMessage&gt; GetRefreshToken(UserRefreshTokenRequest userRefreshTokenRequest)<br>    {<br><br>        var jsonContent = JsonSerializer.Serialize(userRefreshTokenRequest);<br>        var content = new StringContent(jsonContent, Encoding.UTF8, &quot;application/json&quot;);<br>        return await _client.PostAsync(&quot;/User/RefreshToken&quot;, content);<br>    }<br>}</pre><ol><li><strong>ApiSuccessTest</strong>:</li></ol><ul><li><em>Scenario:</em> Requests a secure API resource with a valid access token.</li><li><em>Expected Outcome:</em> Expect a successful response with an HTTP status code of OK.</li></ul><p>2. <strong>ApiFailureTest</strong>:</p><ul><li><em>Scenario:</em> Requests a secure API resource with an empty access token.</li><li><em>Expected Outcome:</em> Expect an unauthorized response with an HTTP status code indicating failure.</li></ul><p><strong>3. TokenExpireTest</strong>:</p><ul><li><em>Scenario:</em> Attempts to use an expired access token to access a secure API resource.</li><li><em>Expected Outcome:</em> Expect an unauthorized response due to the expired token.</li></ul><p><strong>4. GetRefreshTokenTest</strong>:</p><ul><li><em>Scenario:</em> Obtains a new access token using a valid refresh token.</li><li><em>Expected Outcome:</em> Expect a successful response with a new access token, allowing access to a secure API resource.</li></ul><p>5. <strong>RefreshTokenExpireTest</strong>:</p><ul><li><em>Scenario:</em> Attempts to refresh the token with an expired refresh token.</li><li><em>Expected Outcome:</em> Expect a failure response, indicating that the refresh token has expired.</li></ul><h3><strong>Summary</strong></h3><p>This article serves as a detailed guide on establishing integration tests for a .NET 8 Web API, with a specific focus on token authentication. It provides practical examples and scenarios to validate the effectiveness and reliability of the authentication system.</p><h3>Source Code</h3><p>For the complete code and more examples, check out the GitHub repository: <a href="https://github.com/andissanayake/UnifiedApp">UnifiedApp</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1ff49b728faf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ASP.NET 8 Token Authentication for Web API and React with Integration Testing (Part 1: API)]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/c-sharp-programming/token-authentication-asp-net-1e7fdfb838bb?source=rss-e43e08829d98------2"><img src="https://cdn-images-1.medium.com/max/849/1*Y_Jk1i54Ad9M0gxz6nWPPA.png" width="849"></a></p><p class="medium-feed-snippet">Implementing ASP.NET Identity, Integrating React Frontend, and Ensuring Robust Security with Integration Testing</p><p class="medium-feed-link"><a href="https://medium.com/c-sharp-programming/token-authentication-asp-net-1e7fdfb838bb?source=rss-e43e08829d98------2">Continue reading on .Net Programming »</a></p></div>]]></description>
            <link>https://medium.com/c-sharp-programming/token-authentication-asp-net-1e7fdfb838bb?source=rss-e43e08829d98------2</link>
            <guid isPermaLink="false">https://medium.com/p/1e7fdfb838bb</guid>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[jwt-token]]></category>
            <category><![CDATA[aspnet]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[identity]]></category>
            <dc:creator><![CDATA[Akalanka Dissanayake]]></dc:creator>
            <pubDate>Thu, 25 Jan 2024 13:01:55 GMT</pubDate>
            <atom:updated>2024-03-24T14:50:52.407Z</atom:updated>
        </item>
    </channel>
</rss>