- client side application, developed in JavaScript, to send request to server. We will use AJAX or fetch API to make requests to the server
- server application, ASP.NET Core Web API, which will receive the request, process it and respond back to the client application
- Later we will modify the client application to make it React app which will make requests to the Web API. It will be rudimentary application which will just show the AJAX based authorization.
Wednesday, October 30, 2024
AJAX based authorization in ASP.NET Core application
Inter page navigation in ASP.NET Core Razor Pages
Built-in Middleware Examples in ASP.NET Core
Built-in Middleware in ASP.NET Core
- UseWhen
- MapWhen
- UseRouting
- UseEndpoints
UseWhen and MapWhen middleware
ASP.NET Core middleware class
Basic Middleware:
public class SimpleMiddleware
{
private readonly RequestDelegate _next;
public SimpleMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Middleware logic here
await _next(context);
}
}
Middleware with Dependencies:
public class MiddlewareWithDependencies
{
private readonly RequestDelegate _next;
private readonly ILogger<MiddlewareWithDependencies> _logger;
public MiddlewareWithDependencies(RequestDelegate next, ILogger<MiddlewareWithDependencies> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation("Processing request...");
await _next(context);
}
}
Parameterless Middleware:
1. Constructor with RequestDelegate Parameter
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Middleware logic here
await _next(context);
}
}
2. Invoke or InvokeAsync Method
public async Task InvokeAsync(HttpContext context)
{
// Custom processing
await _next(context); // Pass control to the next middleware
}
3. Registered in the Middleware Pipeline
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<CustomMiddleware>();
}
public class DependencyInjectedMiddleware : IMiddleware
{
private readonly IService _service;
public DependencyInjectedMiddleware(IService service)
{
_service = service;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// Middleware logic here
await next(context);
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<DependencyInjectedMiddleware>();
}
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<DependencyInjectedMiddleware>();
}
Summary
- It has a constructor that takes a RequestDelegate parameter (or implements IMiddleware).
- It has an Invoke or InvokeAsync method that takes an HttpContext parameter.
- It is registered in the middleware pipeline using UseMiddleware<T>().
Is explicit constructor in middleware required in ASP.NET Core?
Create a new endpoint in an ASP.NET Core
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
- app.UseEndpoints is used to define the endpoints for the application within the endpoint routing middleware.
- endpoints.MapGet("/", ...) maps an HTTP GET request to the root URL ("/") of the application.
- When a GET request is made to the root URL, the lambda function inside MapGet will execute, sending "Hello World!" as the response. This effectively creates a new endpoint in the app at the "/" route.
- URL (Route Template): The path pattern or template, such as "/" or "/api/products/{id}", that the endpoint responds to.
- HTTP Verb: The specific HTTP method (e.g., GET, POST, PUT, DELETE) that the endpoint should respond to. For example, MapGet restricts the endpoint to only respond to GET requests.
- Execution Logic: The core of an endpoint is often a delegate or function that contains the actual logic to be executed when the endpoint is called. In your example:
- async context => { await context.Response.WriteAsync("Hello World!"); }
- This function defines what the endpoint does upon receiving a request (in this case, it sends "Hello World!" as the response).
- Middleware Pipeline (Filters): You can configure middleware that applies only to specific endpoints. This can include authorization, authentication, or other custom filters that affect how the endpoint handles requests.
- Metadata: Endpoints can include metadata, such as route parameters, custom attributes, and additional data (e.g., [Authorize], [Produces("application/json")]). This metadata can be used by middleware and tools to process the request or control access.
CORS Success and Failure in ASP.NET Core
Tuesday, October 29, 2024
Middleware Example, Daily downtime in ASP.NET Core for specific routes
Host attribute on action method in ASP.NET Core
Important properties and methods of PageModel class in ASP.NET Core Razor Pages
Monday, October 28, 2024
Understanding Endpoint and its Metadata in ASP.NET Core
What is Endpoint?
Routing in ASPNET Core Razor Page
How do PageModel and Page classes work together in ASP.NET Core Razor Pages
Is MVC used in ASP.NET Core Razor Pages?
PageModel in ASP.NET Core Razor Page
Different directives used in ASPNET Core razor page
The @page directive
The @page directive in ASP.NET Core Razor Pages is crucial because it designates a Razor file as a Razor Page, allowing it to handle HTTP requests directly without requiring a controller. This directive is often used in files with the .cshtml extension within Razor Pages applications. Here's how it works and what it enables:
ASP.NET Core - Basics of Razor Page
- To learn about Basics of Razor Page
- Get handler method to get list of products
- Get handler method to get details of a product
- Handler methods naming conventions
- Open the Visual Studio.
- Create web project using ASP.NET Core Razor Page template.
- Project name: ReadJsonData
- Solution name: ReadJsonData
- .NET Core version: 8.0
- Add the AddRazorPages() service in Program class.
- Use MapRazorPages() as terminal middleware in Program class.
namespace ReadJsonData
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
}
}
}
[
{
"id": 1,
"name": "Item 1",
"description": "Description of Item 1"
},
{
"id": 2,
"name": "Item 2",
"description": "Description of Item 2"
}
]
using System.Text.Json.Serialization;
namespace ReadJsonData.Models;
public class Product
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("description")]
public string? Description { get; set; }
}
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div>
<ul>
@foreach(var item in Model.Items)
{
<li>
<strong>@item.Name</strong> - @item.Description
</li>
}
</ul>
</div>
using Microsoft.AspNetCore.Mvc.RazorPages;
using ReadJsonData.Models;
using System.Text.Json;
namespace ReadJsonData.Pages
{
public class IndexModel : PageModel
{
private readonly IWebHostEnvironment _environment;
public List<Product>? Items { get; private set; }
public Product? ItemDetails { get; private set; }
public IndexModel(IWebHostEnvironment env)
{
_environment = env;
}
// The OnGetAsync method fetches data and returns
// which is displayed in razor page. Note that, here in this example, it returns a List<T>
public async Task OnGetAsync()
{
var filePath = Path.Combine(_environment.WebRootPath, "files", "data.json");
if (System.IO.File.Exists(filePath))
{
var jsonData = await System.IO.File.ReadAllTextAsync(filePath);
Items = JsonSerializer.Deserialize<List<Product>>(jsonData);
}
else
{
Items = new List<Product>(); // Empty list if file is not found
}
}
}
}
- OnGetAsync or OnGet for GET requests
- OnPostAsync or OnPostAsync for POST requests
- OnPutAsync or OnPutAsync for PUT requests, and so on
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
public class IndexModel : PageModel
{
private readonly IWebHostEnvironment _environment;
public IndexModel(IWebHostEnvironment environment)
{
_environment = environment;
}
[BindProperty(SupportsGet = true)]
public int Id { get; set; }
public Item ItemDetails { get; private set; }
public async Task<IActionResult> OnGetAsync()
{
var filePath = Path.Combine(_environment.WebRootPath, "files", "data.json");
if (System.IO.File.Exists(filePath))
{
var jsonData = await System.IO.File.ReadAllTextAsync(filePath);
var items = JsonSerializer.Deserialize<List<Item>>(jsonData);
// Find the item with the specified Id
ItemDetails = items?.FirstOrDefault(i => i.Id == Id);
}
if (ItemDetails == null)
{
return NotFound(); // Return 404 if the item is not found
}
return Page();
}
}
@page "{Id:int}"
@model IndexModel
<h2>Item Details</h2>
@if (Model.ItemDetails != null)
{
<p><strong>ID:</strong> @Model.ItemDetails.Id</p>
<p><strong>Name:</strong> @Model.ItemDetails.Name</p>
<p><strong>Description:</strong> @Model.ItemDetails.Description</p>
}
else
{
<p>Item not found.</p>
}
@page
@model IndexModel
Another Approach
- Keeps Code Organized: Separates the list view (all items) from the detail view (individual item), making each page more focused.
- Improves Readability and Maintainability: You avoid complex conditional logic in a single Razor Page to handle both list and detail views.
- Enables Different Layouts: Allows you to use different layouts or styles for the list and detail views if needed.
- Add a New Razor Page in your Pages folder, e.g., Details.cshtml.
- Set up the New Page Model: In the code-behind file (Details.cshtml.cs), set it up to accept an Id parameter, load the JSON data, and filter it for the specific item.
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
public class DetailsModel : PageModel
{
private readonly IWebHostEnvironment _environment;
public DetailsModel(IWebHostEnvironment environment)
{
_environment = environment;
}
[BindProperty(SupportsGet = true)]
public int Id { get; set; }
public Item ItemDetails { get; private set; }
public async Task<IActionResult> OnGetAsync()
{
var filePath = Path.Combine(_environment.WebRootPath, "files", "data.json");
if (System.IO.File.Exists(filePath))
{
var jsonData = await System.IO.File.ReadAllTextAsync(filePath);
var items = JsonSerializer.Deserialize<List<Item>>(jsonData);
// Find the item with the specified Id
ItemDetails = items?.FirstOrDefault(i => i.Id == Id);
}
if (ItemDetails == null)
{
return NotFound();
}
return Page();
}
}
@page "{Id:int}"
@model DetailsModel
<h2>Item Details</h2>
@if (Model.ItemDetails != null)
{
<p><strong>ID:</strong> @Model.ItemDetails.Id</p>
<p><strong>Name:</strong> @Model.ItemDetails.Name</p>
<p><strong>Description:</strong> @Model.ItemDetails.Description</p>
}
else
{
<p>Item not found.</p>
}
@page
@model IndexModel
<h2>Items</h2>
<ul>
@foreach (var item in Model.Items)
{
<li>
<strong>@item.Name</strong> - @item.Description
<a asp-page="./Details" asp-route-id="@item.Id">View Details</a>
</li>
}
</ul>
- Separation of Concerns: The list view (Index.cshtml) and detail view (Details.cshtml) are separated, making each view simpler and more focused.
- Customizability: You can apply distinct layouts, styles, or additional functionality to the detail page without affecting the list page.
public async Task OnGetByIdAsync(int Id)
{
// Your code
}
@page "{Id:int}/by-id"
@model ReadJsonData.Pages.DetailsModel
public void OnGetChangedCountry()
{
// Logic specific to changing the country, like loading cities for the selected country
}
<form method="get">
<select name="country" asp-page-handler="ChangedCountry">
<option value="USA">USA</option>
<option value="Canada">Canada</option>
</select>
<button type="submit">Submit</button>
</form>
ASP.NET Core Razor page Handler, Basic concept
public void OnGet()
{
// Initialization or data fetching logic
}
public void OnGetSomeMethod() // e.g. OnGetChangedCountry
{
// Logic specific to changing the country, like loading cities for the selected country
}
<form method="get">
<select name="country" asp-page-handler="SomeMethod">
<option value="USA">USA</option>
<option value="Canada">Canada</option>
</select>
<button type="submit">Submit</button>
</form>
- OnGet: Default GET request handler, used for general page initialization.
- OnGetSomeMethod: Named GET handler, used for specific actions like handling a country selection change on the page.
- Using named handlers allows for cleaner and more modular code when handling multiple GET actions on a single Razor Page.
Thursday, October 17, 2024
Google Cloud TTS App, Convert Hindi Text into Audio file
Google Text-to-Speech (TTS) service for the Hindi language
Objectives:
In this I have explained how you can develop a Text-ToSpeech Application that will convert Hindi text into Audio MP3 file. The limitations of the app is that it can convert text upto 5000 bytes. You can use chunking the text so that all the words upto 5000 bytes are processed by the Google TTS API at a time. In the first version of the application, I have provided simple example which can process text upto 5000 bytes. Neither text length is checked, nor logging is done in the application to keep the learning clear and concise.
Steps for TTS App Development:
You can use Google Cloud Text-to-Speech API to convert text files into speech, including Hindi text. Here's a step-by-step guide on how to do this:
Steps to Convert Text to Speech (Hindi) using Google Cloud TTS:
- Set up Google Cloud Project:
- Go to Google Cloud Console.
- Sign in and create a new project or select an existing one.
- Enable the Text-to-Speech API from the API library.
- Set up Billing:
- Google Cloud Text-to-Speech requires billing to be enabled on your project, though you can use the free tier for limited use.
- Go to the billing section in the console and add a billing account if you haven't already.
- Create Credentials:
- Go to the Credentials section and create an API key. This key will be used to authenticate API requests.
- Install Google Cloud Client Library for C#:
- To interact with the Google Cloud Text-to-Speech API in C#, you need to install the Google.Cloud.TextToSpeech.V1 package via NuGet
- Write C# Code to Convert Hindi Text to Speech : Here's an example of a C# application that converts a Hindi text file into speech using the Google Cloud TTS API. The code is written in ASP.NET Core API controller named as TTSController. The code will work in .ASPNET Core SDK version 6 and later versions.
- Run the Application
- Replace path/to/your/hindi-text-file.txt with the actual path to your Hindi text file.
- Run the application, and it will generate an MP3 file with the synthesized speech.
using Google.Apis.Auth.OAuth2;
using Google.Cloud.TextToSpeech.V1;
using Microsoft.AspNetCore.Mvc;
namespace ShriWebTTS.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TTSController : ControllerBase
{
private const string GoogleAppCredentials = "GOOGLE_APPLICATION_CREDENTIALS";
[HttpPost("convert-text-to-speech")]
public async Task ConvertTextToSpeechAsync([FromBody] string text)
{
// Replace custom symbols with break tags
text = text.Replace("|", ""); // 1 second pause
text = text.Replace("||", ""); // 2 seconds pause
text = text.Replace("\r\n", " "); // Replace Windows line breaks
// Load the service account key file from the environment variable
string? serviceAccountPath = Environment.GetEnvironmentVariable(GoogleAppCredentials);
if (string.IsNullOrEmpty(serviceAccountPath) || !System.IO.File.Exists(serviceAccountPath))
{
return BadRequest("Service account key file path is not set or file does not exist.");
}
// Create TextToSpeechClient using service account credentials
var credential = GoogleCredential.FromFile(serviceAccountPath);
var textToSpeechClient = TextToSpeechClient.Create(); // No parameters here
// Create SSML input with pitch and volume adjustments
var ssmlInput = $@"
<speak xmlns='http://www.w3.org/2001/10/synthesis' version='1.0'>
<prosody pitch='+5%' volume='+5dB' rate='slow'>
{text}
</prosody></speak>";
var response = await textToSpeechClient.SynthesizeSpeechAsync(new SynthesisInput
{
Ssml = ssmlInput
}, new VoiceSelectionParams
{
LanguageCode = "hi-IN",
SsmlGender = SsmlVoiceGender.Male
}, new AudioConfig
{
AudioEncoding = AudioEncoding.Mp3
});
var outputFilePath = "output.mp3";
// Write the audio content directly to the file
using (var output = System.IO.File.Create(outputFilePath))
{
response.AudioContent.WriteTo(output);
}
return File(System.IO.File.ReadAllBytes(outputFilePath), "audio/mpeg", "output.mp3");
}
}
}
The Swagger API is enabled in the Application. Swagger is used so that you could send text data without using any separate client app. You can do the testing even using Postman but using Swagger helped me to avoid switching to any other app for testing. Look at the Program class coding this regard:
namespace ShriWebTTS;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
Key Components in the Code:
- TextToSpeechClient: This is the Google Cloud client that interacts with the Text-to-Speech API.
- SynthesisInput: Contains the text input that will be converted to speech.
- VoiceSelectionParams: Specifies the voice's language ("hi-IN" for Hindi) and gender.
- AudioConfig: Specifies the output audio format (MP3 in this case).
Additional Notes:
- Ensure that you have authenticated your Google Cloud SDK by setting the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of your service account key JSON file, which can be created in the Google Cloud Console under Credentials.
For large-scale usage, you may also consider configuring more advanced features such as prosody, pitch, and speed settings.
Client side Interface to send text
You can create a client side Interface to send text data to the API application. The following code generates a form with input text area and submit button. In this case, you will have to enable CORS in the TTS Web API app.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title></head>
<body>
<form id="ttsForm">
<label for="text">Enter Hindi Text:</label>
<textarea id="text" name="text"></textarea>
<button type="submit">Convert to Speech</button>
</form>
<audio id="audioPlayer" controls></audio> <script>
document.getElementById('ttsForm').addEventListener('submit', async function (e) {
e.preventDefault();
const text = document.getElementById('text').value;
const response = await fetch('https://your-api-url/api/tts/convert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
const audioBlob = await response.blob();
const audioUrl = URL.createObjectURL(audioBlob);
document.getElementById('audioPlayer').src = audioUrl;
});
</script></body></html>
As pointed before that if you're testing the API from a web front-end during development, you might need to enable CORS to allow requests from your local web browser. In Startup.cs, enable CORS for your development environment. The code will be as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowLocalhost",
builder =>
{
builder.WithOrigins("http://localhost:3000") // Adjust based on your front-end URL
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseCors("AllowLocalhost"); // Enable CORS for development
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
OAuth Setup Steps
The OAuth consent screen is the interface that Google users see when your app requests permission to access their data. You need to provide certain information about your application to users, such as the name of your app, the scope of data you want to access, and your privacy policy.
Steps to Configure the OAuth Consent Screen
- Go to the Google Cloud Console:
- Navigate to the Google Cloud Console.
- Select or Create a Project:
- If you haven't already done so, make sure you have selected the appropriate Google Cloud project or create a new one.
- Open the OAuth Consent Screen Configuration:
- From the left-hand navigation menu, go to APIs & Services > OAuth consent screen.
- Choose the User Type:
- You will be asked to choose the user type:
- Internal: Only available to users within your Google Workspace organization.
- External: Available to any Google account holder (used for public-facing apps). This is the most common option for most web and mobile apps.
- Select the appropriate user type and click Create.
- Fill in the Consent Screen Details:
- App Name: The name of your app that users will see.
- User Support Email: An email that users can contact for support.
- App Logo (optional): You can upload a logo for your app, which will appear on the consent screen.
- App Domain (Optional): Provide your website or app domain (optional, but recommended if you have a public-facing app).
- Authorized Domains: Add the domains that are associated with your app (e.g., yourapp.com).
- Developer Contact Information: Provide the developer's contact information, such as your email.
- Scopes:
- Scopes define what kind of access your app is requesting. By default, Google Cloud will include basic profile information, but you can add other specific API scopes if your application requires access to additional user data.
- Summary and Submit:
- Review the consent screen summary. You can save it as a draft if you’re still working on it or submit it for verification if you're using sensitive or restricted scopes.
- Verification (Optional):
- If your app requests sensitive scopes (like user email or profile info), Google might require a verification process to ensure your app is secure. If you’re using non-sensitive scopes, you may not need this.
- Finish:
- After configuring the consent screen, you'll be able to go back to the Credentials section and create your OAuth Client ID.
Now you can create an OAuth Client ID:
Once the consent screen is configured:
- Go back to the Credentials page in APIs & Services.
- Click on Create Credentials > OAuth 2.0 Client IDs.
- Select the application type (Web Application, Desktop, etc.).
- Fill in the necessary fields (e.g., Authorized Redirect URIs).
- Click Create.
This will generate your OAuth Client ID and Client Secret.
Conclusion:
The OAuth consent screen setup is a mandatory step that ensures transparency for users when your app requests access to their data. Once completed, you can proceed to create the OAuth client ID and integrate it into your application.
Wednesday, October 2, 2024
Fluent API and its Characteristics in ASP.NET Core?
The term Fluent API refers to a style of coding in which methods are chained together in a way that is readable and expressive, resembling natural language. It allows developers to configure objects or frameworks through method chaining, making the code more intuitive and easier to understand.
Key Characteristics of Fluent API
- Method Chaining: Methods return the object they belong to, allowing multiple methods to be called in a chain-like sequence.
- Readability: The API is designed to be readable, making it look like a natural language expression, which improves clarity and reduces code complexity.
- Configurability: It allows for flexible configuration of objects and frameworks without requiring complex XML configurations or annotations. Example in ASP.NET Core: In ASP.NET Core, Fluent API is used extensively, especially in the WebApplicationBuilder and middleware configuration. Below is an example:
var app = WebApplication.CreateBuilder(args).Build();
app.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
In this example:
- Each method (e.g., UseRouting(), UseAuthentication(), UseAuthorization()) is called on the app object and returns the same object, allowing further methods to be called in sequence.
- The chain of methods provides a readable, step-by-step configuration of the middleware pipeline.
- Example of configuring a one-to-many relationship using Fluent API:
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithOne(p => p.Blog)
.HasForeignKey(p => p.BlogId);
This code configures the Blog entity to have many Posts and sets up the relationship with a foreign key using Fluent API methods.
Benefits of Fluent API:
- Clarity: It allows you to express configurations clearly and concisely.
- Flexibility: It provides more customization and flexibility than attributes or annotations.
- Chaining: Each method returns the object it operates on, allowing multiple methods to be chained together in a single statement.
Is Method Chaining Compulsory In fluent API?
No, method chaining is not compulsory in Fluent API. While method chaining is a common feature of Fluent APIs, it is not a strict requirement. The core idea behind a Fluent API is to offer a more expressive and readable way to configure or interact with objects, which often involves method chaining but doesn’t always require it.
Fluent API Without Method Chaining: You can still use a Fluent API without chaining methods if that suits your coding style or needs. Each method can be called individually without chaining.
- Example without Method Chaining:
var app = WebApplication.CreateBuilder(args).Build();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
In this example:
- Each method (UseRouting(), UseAuthentication(), UseAuthorization(), etc.) is called individually without chaining, but the configuration still follows the Fluent API pattern.
When is Method Chaining Useful?
Method chaining is useful when:
- Improving Readability: It creates a more compact and readable flow, resembling natural language.
- Reducing Boilerplate: It avoids the need to re-reference the object in each line, making the code shorter.
- Configuring in Sequence: When configuring objects step-by-step, chaining provides a structured way to express this configuration.
- When it reduces clarity: Sometimes chaining many methods together can make the code less readable or harder to debug, especially if the chain becomes very long.
- When order matters: In some cases, the order in which methods are called is important. Breaking the chain into individual statements can make this clearer.
Conclusion:
While method chaining is a hallmark of Fluent APIs, it is not compulsory. The key idea of Fluent API is to make the code more expressive and readable, which can be achieved with or without chaining methods. Chaining is just a convenience that enhances readability in many cases.
How to recognize whether API is fluent or not?
To recognize whether an API is Fluent, you can look for certain key characteristics that distinguish it from other coding styles. A Fluent API primarily aims to make the code more readable, expressive, and intuitive by allowing the use of method chaining and resembling natural language expressions.
An API is fluent based on various indicators. Some of them are as follows:
1. Method Chaining:
- The API allows calling multiple methods on an object in sequence (chaining) where each method returns the same object or another object, allowing for further method calls.
- You can keep calling methods one after another without needing to re-reference the object. Example:
var app = WebApplication.CreateBuilder(args).Build();
app.UseRouting()
.UseAuthentication()
.UseAuthorization();
In this example, the methods (UseRouting(), UseAuthentication(), UseAuthorization()) are chained together, and each method returns the same object (app) to allow further method calls. This is a common Fluent API pattern.
2. Expressive and Readable:
- The API is designed to read like natural language or a series of instructions. It should be easy to understand what the code is doing by just reading it.
- The code is intuitive, concise, and expresses its purpose clearly without much complexity. Example:
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
This is expressive because it clearly tells you it's building a configuration by adding a JSON file and environment variables.
3. Methods Returning the Same Object:
- Each method call typically returns the same instance (called self-referencing) so that further method calls can be made on that instance.
- After each method call, the same object or an object of the same type is returned, allowing further configuration without breaking the chain. Example:
var query = dbContext.Users.Where(u => u.IsActive)
.OrderBy(u => u.Name)
.Select(u => new { u.Name, u.Email });
Here, each method (Where(), OrderBy(), Select()) returns the same type (an IQueryable object) and enables further chaining.
4. Optional Parameters or Configuration:
- The Fluent API often allows you to pass parameters or configure objects in a step-by-step manner.
- The API exposes configuration options through methods that set specific properties or configurations. Example:
var builder = new StringBuilder();
builder.Append("Hello")
.AppendLine("World")
.Insert(0, "Say: ");
Each method (Append(), AppendLine(), Insert()) configures the StringBuilder object in a modular and flexible way.
5. Immutability in Design (Optional):
- Some Fluent APIs enforce immutability, meaning each method call returns a new instance with updated state rather than modifying the existing object.
- You can chain methods, but the object itself is not modified. Instead, a new instance is created with the desired configuration. Example (in LINQ):
var list = new List<int> { 1, 2, 3, 4 };
var evenNumbers = list.Where(n => n % 2 == 0).ToList();
The Where() method does not modify the original list but instead returns a new sequence with only the filtered values.
6. Minimal Boilerplate Code:
- The API minimizes the need for repetitive code or excessive scaffolding.
- Instead of multiple lines of code to perform a task, a Fluent API provides a concise and natural way to express operations. Example:
var logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs.txt")
.CreateLogger();
This is concise and avoids boilerplate code for configuring multiple logging targets.
Summary of Key Characteristics:
- Method Chaining: Ability to chain method calls.
- Readability and Expressiveness: Code reads like a set of natural instructions.
- Return Same Object: Methods return the same object for further calls.
- Optional Parameters/Configurations: Flexible configuration in steps.
- Immutability (Optional): Some Fluent APIs may follow an immutable design.
- Minimal Boilerplate: Code is concise and avoids excessive repetition.
Conclusion:
To recognize if an API is Fluent, check for method chaining, readable and expressive code, return values that enable further method calls, and minimal boilerplate. While method chaining is a common aspect, the overall goal of a Fluent API is to provide an intuitive, flexible, and expressive way to configure or manipulate objects or workflows.
Hot Topics
-
In this post we will learn what is stored procedure, how it is different from query and what are its advantages over queries. Procedures I...
-
Objectives To provide detailed information about ListBox Types of ListBox Using ListBox in VBA applications Please read the post till end...
-
ACID Properties To understand transaction in depth, we must know all about its properties. We will look at ACID properties which are rela...