C# is a topic the MagnusMinds engineering team writes about from project work: how it is set up, where it breaks, and what we changed in production. This tag page lists all 14 articles tagged C#.
Start with “Exploring the Latest Features in C# 10”, “Secure Authentication and Authorization in .NET Core”, or “Mastering Dependency Injection in .NET Core Applications”. Open a post for the full write-up, or follow another tag or category on the card to a related topic.
Exploring the Latest Features in C# 10 C# 10, the latest version of the C# programming language, brings exciting new features and enhancements that aim to improve developer productivity, code readability, and overall language capabilities. In this blog post, we'll take a closer look at some of the notable features introduced in C# 10. 1. Record Types Improvements Record types, introduced in C# 9, have proven to be a valuable addition for simplifying immutable data structures. In C# 10, record types receive enhancements that make them even more powerful. Example: csharpCopy code public record Person { public string FirstName { get; init; } public string LastName { get; init; } } // C# 10 allows you to simplify the instantiation of record types var person = new Person { FirstName = "John", LastName = "Doe" }; In C# 10, you can use the with expression to create a copy of a record with modified values more concisely: csharpCopy code var updatedPerson = person with { FirstName = "Jane" }; This syntax improves the readability of code when updating record instances. 2. Parameter Null Checking C# 10 introduces the notnull modifier for parameters, enabling developers to enforce non-null arguments at the call site. This can lead to more robust code by catching potential null reference exceptions early. Example: public void ProcessData(notnull string data) { // The 'data' parameter is guaranteed to be non-null within this method } The notnull modifier serves as a contract, making it clear that the method does not accept null arguments. 3. Global Usings C# 10 simplifies the process of importing namespaces by introducing global usings. Now, you can include common namespaces globally, reducing the need to include them in each file. Example: // C# 10 global using global using System; global using System.Collections.Generic; class Program { static void Main() { List<string> myList = new(); // ... } } By using global usings, you can make your code more concise and improve overall code readability. 4. File-scoped Namespace Declarations C# 10 introduces file-scoped namespace declarations, allowing you to define namespaces directly at the file level. This simplifies the organization of code and reduces the need for excessive indentation. Example: csharpCopy code // File-scoped namespace declaration namespace MyNamespace; class MyClass { // ... } This feature promotes cleaner code structure, making it easier to navigate and maintain. 5. Extended Property Patterns C# 10 enhances property patterns, enabling more expressive and concise matching when working with switch statements and patterns. Example: csharpCopy code public class Point { public int X { get; set; } public int Y { get; set; } } var point = new Point { X = 5, Y = 10 }; // C# 10 extended property patterns var result = point switch { { X: 0, Y: 0 } => "Origin", { X: var x, Y: var y } when x == y => "Diagonal", _ => "Unknown" }; These improvements make pattern matching even more powerful and expressive. Conclusion C# 10 introduces several features and enhancements that aim to make the language more expressive, concise, and developer-friendly. By leveraging these new capabilities, developers can write cleaner, more maintainable code. As always, staying updated with the latest language features empowers developers to make the most of the tools at their disposal. Explore these features in your projects and embrace the evolution of C# for a more enjoyable and efficient development experience.
Authentication and authorization are essential components of any web application, ensuring the security and proper access control for users. In NET Core, these concepts play a crucial role in protecting resources and determining user permissions. Authentication in NET Core Authentication is the process of verifying the identity of a user, ensuring they are who they claim to be. This is typically done by presenting credentials, such as a username and password, and validating them against a trusted source, such as a database or an external authentication provider. Once authenticated, the user is assigned an identity, which is then used for subsequent authorization checks. Authentication in NET Core Authentication in NET Core revolves around the concept of authentication schemes. An authentication scheme represents a specific method or protocol used to authenticate users. NET Core supports various authentication schemes out of the box, including cookie authentication, JWT bearer authentication, and external authentication providers like OAuth and OpenID Connect. Understanding Authentication Schemes Authentication schemes are registered in the application’s startup class using the AddAuthentication method. This method allows you to specify one or more authentication schemes and their respective options. For example, to enable cookie authentication, you can use the AddCookie services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { // Configure CookieAuthenticationDefaults options }); Configuring Cookie Authentication To configure cookie authentication, you need to specify the authentication scheme as CookieAuthenticationDefaults.AuthenticationScheme and provide the necessary options, such as the cookie name, login path, and authentication endpoint. Here's an example: services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.Name = "MyCookie"; options.LoginPath = "/Admin/Login"; }); In this example, the cookie authentication middleware is configured to use the scheme named “MyCookie” and redirect users to the “/Admin/Login” page if they try to access a protected resource without being authenticated. The options object allows you to customize various aspects of cookie authentication, such as cookie expiration and sliding expiration. Implementing Claim-Based Authentication A claim represents a piece of information about the user, such as their name, email address, or role. By using claims, you can easily extend the user’s identity with additional data and make authorization decisions based on these claims. In NET Core, claim-based authentication is implemented using the ClaimsIdentity and ClaimsPrincipal classes. The ClaimsIdentity represents a collection of claims associated with a user, while the ClaimsPrincipal represents the user's identity as a whole. When a user is authenticated, their claims are stored in a ClaimsPrincipal, which is then attached to the current request's HttpContext.User property. To implement claim-based authentication, you need to create and populate a ClaimsIdentity object with the relevant claims. This can be done during the authentication process, typically in a custom authentication handler. Here's an example of how to create a ClaimsIdentity with a username claim: var claims = new List<Claim> { new Claim(ClaimTypes.Name, "Himanshu") }; var identity = new ClaimsIdentity(claims, "MyAuthenticationScheme"); var principal = new ClaimsPrincipal(identity); await HttpContext.SignInAsync(principal); External Authentication Providers External authentication allows users to sign in to your application using their existing accounts from popular platforms like Google, Facebook, Twitter, and Microsoft. To enable external authentication, you need to configure the desired authentication provider and register it in your application’s startup class. services.AddAuthentication() .AddGoogle(options => { options.ClientId = "YOUR_GOOGLE_CLIENT_ID"; options.ClientSecret = "YOUR_GOOGLE_CLIENT_SECRET"; }); Securing APIs with JWT Bearer Authentication .NET Core provides built-in support for securing APIs using JSON Web Tokens (JWT) and the JWT bearer authentication scheme. JWTs are self-contained tokens that contain information about the user and their permissions. By validating the integrity and authenticity of a JWT, you can trust the claims it contains and authenticate API requests. To enable JWT bearer authentication, you need to configure the authentication scheme and provide the necessary options, such as the token validation parameters and the issuer signing key. Here’s an example of configuring JWT bearer authentication: services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateIssuerSigningKey = true, ValidIssuer = "YOUR_ISSUER", ValidAudience = "YOUR_AUDIENCE", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YOUR_SIGNING_KEY")) }; }); In this example, the AddJwtBearer extension method is used to configure JWT bearer authentication. The TokenValidationParameters object is set with the necessary validation rules, such as validating the issuer, audience, and the issuer's signing key. You need to replace the placeholder values with your own values specific to your JWT setup. With JWT bearer authentication enabled, API endpoints can be protected by applying the [Authorize] attribute to the corresponding controller or action. This ensures that only requests with valid and authenticated JWTs are allowed access to the protected resources. Maintain secure Authorization Authorization in NET Core is primarily controlled through the use of the [Authorize] attribute. This attribute can be applied at the controller or action level to restrict access to specific components of your application. By default, the [Authorize] attribute allows only authenticated users to access the protected resource. The Role of Authorize Attribute : For example, you can use the [Authorize(Roles = "Admin")] attribute to restrict access to administrators only. This ensures that only users with the "Admin" role can access the protected resource. Restricting Access with Policies : While the [Authorize] attribute provides a simple way to restrict access, ASP.NET Core also supports more advanced authorization policies. Authorization policies allow you to define fine-grained rules for determining whether a user is authorized to perform a specific action. To use authorization policies, you need to define them in your application’s startup class using the AddAuthorization method. Here's an example: services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => { policy.RequireRole("Admin"); }); }); Rrole-based authorization can be implemented using the built-in role-based authentication system or by integrating with an external identity provider, such as Active Directory or Azure AD. Implementing Two-Factor Authentication Two-factor authentication (2FA) adds an extra layer of security to the authentication process by requiring users to provide additional verification, typically in the form of a one-time password or a biometric factor. Implementing 2FA can significantly reduce the risk of unauthorized access, especially for sensitive applications or those handling confidential information. To implement two-factor authentication, you need to configure the desired authentication providers, such as SMS, email, or authenticator apps, and register them in your application’s startup class. You also need to configure the necessary options, such as the message templates or the issuer signing key. By enabling two-factor authentication, you provide an additional layer of security that can help protect user accounts from unauthorized access, even if their credentials are compromised. Protecting Against Common Security Vulnerabilities When implementing authentication and authorization in your application, it’s crucial to be aware of common security vulnerabilities and take appropriate measures to prevent them. By understanding these vulnerabilities and following security best practices, you can ensure the integrity and confidentiality of user data. Some common security vulnerabilities to consider when implementing authentication and authorization include: Cross-Site Scripting (XSS): Protect against XSS attacks by properly encoding user input and validating data before rendering it in HTML or JavaScript. Cross-Site Request Forgery (CSRF): Implement CSRF protection mechanisms, such as anti-forgery tokens, to prevent attackers from executing unauthorized actions on behalf of authenticated users. Brute-Force Attacks: Implement account lockout policies and rate limiting to protect against brute-force attacks that attempt to guess user credentials. Session Management: Use secure session management techniques, such as session timeouts, secure cookie attributes, and session regeneration, to prevent session hijacking or session fixation attacks. Password Storage: Store passwords securely by using strong hashing algorithms, salting, and iteration counts to protect against password cracking attempts. By addressing these vulnerabilities and following security best practices, you can minimize the risk of unauthorized access, data breaches, and other security incidents. Conclusion: Authentication and authorization are critical components of building secure and robust web applications in .Net Core. By understanding the concepts and leveraging the powerful features provided by. NET Core, developers can implement robust security measures to protect their applications and ensure that users access resources securely and efficiently.
Dependency Injection (DI) is a core design pattern in .NET Core, enabling developers to build flexible, maintainable, and testable applications. By decoupling the creation and management of dependencies from the business logic, DI helps create loosely coupled systems that are easier to manage and evolve. This blog will guide you through mastering Dependency Injection in .NET Core applications, covering the basics to advanced usage. What is Dependency Injection? Dependency Injection is a design pattern where an object receives its dependencies from an external source rather than creating them itself. In simpler terms, it allows objects to be injected with their required dependencies, promoting loose coupling and enhancing testability. Types of Dependency Injection: Constructor Injection: Dependencies are provided through a class constructor. Property Injection: Dependencies are set through public properties. Method Injection: Dependencies are passed through method parameters. Why Use Dependency Injection? Loose Coupling: Reduces dependencies between components, making them easier to manage and test. Enhanced Testability: Mock dependencies can be easily injected, facilitating unit testing. Flexibility: Allows for easy swapping of implementations without modifying the dependent classes. Configuration: Centralizes configuration for object creation, making it easier to manage. Implementing Dependency Injection in .NET Core In .NET Core, the DI framework is built-in and tightly integrated with the framework, making it easy to use in any .NET Core application. 1. Registering Services Services are registered in the ConfigureServices method in the Startup.cs file. The framework provides three lifetimes for service registration: Transient: A new instance is created every time the service is requested. Scoped: A new instance is created per request. Singleton: A single instance is created and shared throughout the application's lifetime. public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMyService, MyService>(); // Transient services.AddScoped<IMyService, MyService>(); // Scoped services.AddSingleton<IMyService, MyService>(); // Singleton } 2. Injecting Services Once registered, services can be injected into controllers, services, or any other classes via constructor injection. public class MyController : Controller { private readonly IMyService _myService; public MyController(IMyService myService) { _myService = myService; } public IActionResult Index() { var result = _myService.DoSomething(); return View(result); } } 3. Using DI in Middleware Middleware components in the request pipeline can also use Dependency Injection. public class MyMiddleware { private readonly RequestDelegate _next; private readonly IMyService _myService; public MyMiddleware(RequestDelegate next, IMyService myService) { _next = next; _myService = myService; } public async Task InvokeAsync(HttpContext context) { _myService.DoSomething(); await _next(context); } } Register the middleware in the Configure method: public void Configure(IApplicationBuilder app) { app.UseMiddleware<MyMiddleware>(); } Advanced Scenarios: 1. Conditional Dependency Resolution You can conditionally resolve dependencies using IServiceProvider or IHttpContextAccessor for scenarios where the dependency may vary based on context. public class MyService : IMyService { private readonly IAnotherService _anotherService; public MyService(IServiceProvider serviceProvider) { _anotherService = serviceProvider.GetService<IAnotherService>(); } } 2. Service Lifetime Management Understanding service lifetimes is crucial, especially when mixing services with different lifetimes. Singleton services should not capture scoped or transient dependencies as it can cause memory leaks or unexpected behavior. Scoped services should avoid holding transient dependencies beyond the request scope. 3. Using the Options Pattern The Options pattern is a technique for handling configuration in .NET Core using DI. It allows you to register and configure POCOs as services. public class MyOptions { public string Option1 { get; set; } } public void ConfigureServices(IServiceCollection services) { services.Configure<MyOptions>(Configuration.GetSection("MyOptions")); } public class MyService : IMyService { private readonly MyOptions _options; public MyService(IOptions<MyOptions> options) { _options = options.Value; } } Best Practices for Dependency Injection Avoid Service Locator Pattern: Using IServiceProvider excessively is considered an anti-pattern as it hides dependencies. Favor Constructor Injection: It makes dependencies explicit and promotes immutability. Register Interfaces, Not Implementations: Register interfaces or abstract classes to decouple the implementation from the interface. Keep Services Small and Focused: Adhere to the Single Responsibility Principle (SRP) to ensure services do one thing well. Conclusion Mastering Dependency Injection in .NET Core applications is a key skill for any .NET developer. By understanding the different types of DI, how to implement it, and best practices, you can build applications that are more modular, testable, and maintainable. Whether you’re just starting with DI or looking to deepen your understanding, the concepts and techniques covered in this blog will help you harness the full power of Dependency Injection in your .NET Core projects.
Before diving into optimization techniques, it’s important to identify the areas of your code that require improvement. By measuring and profiling your application’s performance, you can pinpoint the exact bottlenecks and focus your optimization efforts where they matter the most (Measure and Identify Bottlenecks). In this blog, I’ll explain effective strategies for handling memory and reducing garbage collection overhead in your C# applications. Memory management and garbage collection are essential aspects of performance tuning in C#, so these best practices will help you optimize your code for maximum efficiency. Here are 8 tips that will help with performance optimization. 1. Use the IDisposable interface : Utilizing the IDisposable interface is a crucial C# performance tip. It helps you properly manage unmanaged resources and ensures that your application’s memory usage is efficient. Bad way: public class ResourceHolder { private Stream _stream; public ResourceHolder(string filePath) { _stream = File.OpenRead(filePath); } // Missing: IDisposable implementation } Good way: public class ResourceHolder : IDisposable { private Stream _stream; public ResourceHolder(string filePath) { _stream = File.OpenRead(filePath); } public void Dispose() { _stream?.Dispose(); // Properly disposing the unmanaged resource. } } By implementing the IDisposable interface, you ensure that unmanaged resources will be released when no longer needed, preventing memory leaks and reducing pressure on the garbage collector. This is a fundamental code optimization technique in C# that developers should utilize. 2. Asynchronous Programming with async/await Asynchronous programming is a powerful technique for improving C# performance in I/O-bound operations, allowing you to enhance your app’s responsiveness and efficiency. Here, we’ll explore some best practices for async/await in C#. Limit the number of concurrent operations Bad way: public async Task ProcessManyItems(List<string> items) { var tasks = items.Select(async item => await ProcessItem(item)); await Task.WhenAll(tasks); } Good way: public async Task ProcessManyItems(List<string> items, int maxConcurrency = 10) { using (var semaphore = new SemaphoreSlim(maxConcurrency)) { var tasks = items.Select(async item => { await semaphore.WaitAsync(); // Limit concurrency by waiting for the semaphore. try { await ProcessItem(item); } finally { semaphore.Release(); // Release the semaphore to allow other operations. } }); await Task.WhenAll(tasks); } } Without limiting concurrency, many tasks will run simultaneously, which can lead to heavy load and degraded overall performance. Instead, use a SemaphoreSlim to control the number of concurrent operations. 3. UseConfigureAwait(false) when possible ConfigureAwait(false) is a valuable C# performance trick that can help prevent deadlocks in your async code and improve efficiency by not forcing continuations to run on the original synchronization context. public async Task<string> DataAsync() { var data = await ReadDataAsync().ConfigureAwait(false); // Use ConfigureAwait(false) to avoid potential deadlocks. return ProcessData(data); } 4. Parallel Computing and Task Parallel Library This will help the power of multicore processors and speed up CPU-bound operations Bad way: private void Data(List<int> data) { for (int i = 0; i < data.Count; i++) { PerformExpensiveOperation(data[i]); } } Good way: private void Data(List<int> data) { Parallel.ForEach(data, item => PerformExpensiveOperation(item)); } Parallel loops can considerably accelerate processing of large collections by distributing the workload among multiple CPU cores. Switch from regular for and foreach loops to their parallel counterparts whenever it’s feasible and safe. 5. Importance of Caching Data Utilizing in-memory caching can drastically reduce time-consuming database fetches and speed up your application. The good way demonstrates the use of in-memory caching to store product data and reduce time-consuming database fetches. 6. Optimizing LINQ Performance Force immediate execution using ToList() or ToArray() when needed. Use the AsParallel() extension method to ensure safety and parallelism. Selecting a HashSet instead of a List offers faster look-up times and greater performance 7. Task and ValueTask for reusing asynchronous code Use ValueTask to reduce heap allocations public async ValueTask<string> DataAsync() { var data = await ReadFromStreamAsync(_stream); return ProcessData(data); } By switching from Task<TResult> to ValueTask<TResult>, you can reduce heap allocations and ultimately improve your C# performance 8. Use HttpClientFactory to manage HttpClient instances private readonly HttpClient _httpClient; public MyClass(HttpClient httpClient) { _httpClient = httpClient; } public async Task GetDataAsync() { var response = await _httpClient.GetAsync("http://himashu.com/data"); } This approach manages the lifetimes of your HttpClient instances more efficiently, preventing socket exhaustion. - Use null-coalescing operators (??, ??=) string datInput = NullableString() ?? "default"; - Using Span and Memory for efficient buffer management // Using Span<T> avoids additional memory allocation and copying byte[] data = GetData(); Span<byte> dataSpan = data.AsSpan(); ProcessData(dataSpan); - Use StringComparison options for efficient string comparison bool equal = string.Equals(string1, string2, StringComparison.OrdinalIgnoreCase); - Use StringBuilder over string concatenation in loops StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.AppendFormat("Iteration: {0}", i); } string result = sb.ToString(); This has been a collection of just a few things I’ve found useful for enhancing the performance of my C# .NET code. Remember that the key to successful development is a balance between code quality and performance optimizations. By employing these techniques, you’ll be able to build high-performing C# applications that deliver a seamless user experience.
In the world of software development and testing, having access to realistic and diverse data sets is crucial. That's why we are thrilled to introduce IndiGen, a powerful and versatile package designed to generate realistic Indian data with ease. Why IndiGen? IndiGen is a comprehensive tool that caters specifically to the needs of developers and testers who require authentic Indian data for their projects. Whether you are working on unit tests, creating sample data, or validating functionality, IndiGen has got you covered. Key Features Realistic Indian Names: Generate complete names, first names, last names, middle names, prefixes, and suffixes. var fullName = India.Faker.Name.FullName(); // Example: Ramesh Babu var firstName = India.Faker.Name.First(); // Example: Amitabh var lastName = India.Faker.Name.Last(); // Example: Kapoor var middleName = India.Faker.Name.Middle(); // Example: Hrutvik var prefix = India.Faker.Name.Prefix(); // Example: Shri var suffix = India.Faker.Name.Suffix(); // Example: Bhai, Kumar Valid Phone Numbers: Generate realistic Indian phone numbers. var phoneNumber = India.Faker.Phone.Number(); // Example: +91-9988776655, 9998887770, 079-27474747 Authentic Vehicle Number Plates: Generate vehicle number plates in Indian formats. var vehicleNumberPlate = India.Faker.VehicleNumberPlate.Number(); // Example: GJ 01 AA 7777, 24 BH 9999 AA Valid PAN Card Numbers: Generate PAN card numbers that conform to Indian standards. var panCardNumber = India.Faker.PanCardNumber.Number(); // Example: AABBB8888A Aadhaar Card Numbers: Generate Aadhaar card numbers. var aadhaarCardNumber = India.Faker.AadharCardNumber.Number(); // Example: 2222 4444 2222 Supported Versions IndiGen is compatible with a wide range of .NET versions, ensuring flexibility and ease of integration into your projects: .NET Framework 4.5, 4.6, 4.7, 4.8 .NET Standard 2.0, 2.1 .NET Core 3.0, 3.1 .NET 5.0, 6.0 Get Started with IndiGen Getting started with IndiGen is simple. Visit our NuGet package page and integrate it into your projects to start generating realistic Indian data today. How to Install Installing IndiGen is straightforward. You can add it to your project using the NuGet Package Manager, .NET CLI, or by editing your project file. Using NuGet Package Manager Open your project in Visual Studio. Go to Tools > NuGet Package Manager > Manage NuGet Packages for Solution. Search for IndiGen. Select the package and click Install. Using .NET CLI Run the following command in your terminal:\ dotnet add package IndiGen Editing Your Project File Add the following line to your .csproj file: <PackageReference Include="IndiGen" Version="8.0.1" /> Replace "8.0.1" with the latest version of IndiGen. NuGet Package: IndiGen Contribute to IndiGen We welcome contributions from the community. If you have suggestions, improvements, or new features in mind, please open an issue or submit a pull request. Together, we can make IndiGen even better! IndiGen is here to simplify your development and testing process by providing realistic Indian data. Try it out and let us know your thoughts. Happy coding!
Introduction In the ever-evolving landscape of web development, simplicity is key. Enter Minimal APIs in ASP.NET Core, a lightweight and streamlined approach to building web applications. In this detailed blog, we'll explore the concept of Minimal APIs, understand why they matter, and walk through their implementation in ASP.NET Core. When to Use Minimal APIs? Minimal APIs are well-suited for small to medium-sized projects, microservices, or scenarios where a lightweight and focused API is sufficient. They shine in cases where rapid development and minimal ceremony are top priorities. You can find in this blog <link> how to create minimal api. I am directly showing the comparison between MinimalAPI and controller. Controllers: Structured and Versatile Controllers, deeply rooted in the MVC pattern, have been a cornerstone of ASP.NET API development for years. They provide a structured way to organize endpoints, models, and business logic within dedicated controller classes. Let's consider an example using Microsoft.AspNetCore.Mvc; namespace MinimalAPI.Controllers { [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet(Name = "GetWeatherForecast")] public IEnumerable<WeatherForecast> Get() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } } } Advantages of Controllers in Action Structure and Organization: Controllers offer a clear structure, separating concerns and enhancing maintainability. Flexibility: They enable custom routes, complex request handling, and support various HTTP verbs. Testing: Controllers facilitate unit testing of individual actions, promoting a test-driven approach Minimal APIs: Concise and Swift With the advent of .NET 6, Minimal APIs emerged as a lightweight alternative, aiming to minimize boilerplate code and simplify API creation. Here's an example showcasing Minimal APIs. using MinimalAPI; 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(); app.MapGet("/GetWeatherForecast", () => { var rng = new Random(); var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; var weatherForecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index).Date, TemperatureC = rng.Next(-20, 55), Summary = summaries[rng.Next(summaries.Length)] }).ToArray(); return Results.Ok(weatherForecasts); }); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); Advantages of Minimal APIs in Focus Simplicity: Minimal APIs drastically reduce code complexity, ideal for smaller projects or rapid prototyping. Ease of Use: They enable quick API creation with fewer dependencies, accelerating development cycles. Potential Performance Boost: The reduced overhead might lead to improved performance, especially in smaller applications. What you choose between MinimalAPI and Controller? Choosing between Controllers and Minimal APIs hinges on various factors. Project Scale: Controllers offer better organization and structure for larger projects with intricate architectures. Development Speed: Minimal APIs shine when speed is crucial, suitable for rapid prototyping or smaller projects. Team Expertise: Consider your team's familiarity with MVC patterns versus readiness to adopt Minimal APIs. Conclusion The decision between Controllers and Minimal APIs for .NET APIs isn't about one being superior to the other. Rather, it's about aligning the choice with the project's specific needs and constraints. Controllers offer robustness and versatility, perfect for larger, complex projects. On the other hand, Minimal APIs prioritize simplicity and rapid development, ideal for smaller, more straightforward endeavours.
What is Authorization? Authorization verifies whether a user has permission to use specific applications or services. While authentication and authorization are distinct processes, authentication must precede authorization, ensuring the user's identity is confirmed before determining their access rights. When logging into a system, a user must provide credentials like a username and password to authenticate. Next, the authorization process grants rights. For example, an administrative user can create a document library to add, edit, and delete documents, while a non-administrative user can only read documents in the library. Types of Authorization: Simple Authorization Role-Based Authorization Claim-Based Authorization Policy-Based Authorization I have implemented an example of role-based authorization in .NET. Step 1: Create one new MVC Web Application with the Authentication type “Individual Account”. Step 2: Register Identity with DefaultTokenProvider in the program.cs file. builder.Services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = false) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); For better understanding, I have added one page to add a new role. Create a new method in the controller and add the following code. [HttpGet] public IActionResult Admin() { return View(); } Create a new model Role.cs. namespace Authorization.Models { public class Role { public string RoleName { get; set; } } } Create html page for add role. @model Role @{ ViewData["Title"] = "Admin"; } <h1>Admin</h1> <div class="row"> <div class="col-md-12"> <form method="post" action="@Url.Action("Admin","Home")"> <div class="form-group"> <label>Role Name</label> <input type="text" class="form-control" style="width:30%;" asp-for="RoleName" placeholder="Role name" required> </div> <br /> <button class="btn btn-success" type="submit">Add</button> </form> </div> </div> I have created a simple page, You can modify the page as per your requirements. Add a new method in the controller and add the following code. And declare RoleManager<IdentityRole> and inject in the constructor. private readonly RoleManager<IdentityRole> _roleManager; public HomeController(RoleManager<IdentityRole> roleManager) { _roleManager = roleManager; } [HttpPost] public async Task<IActionResult> Admin(Role role) { var result = _roleManager.RoleExistsAsync(role.RoleName).Result; if (!result) { await _roleManager.CreateAsync(new IdentityRole(role.RoleName)); } return RedirectToAction("Admin"); } Set a new tab in _Layout.cshtml file to redirect to the Add role page. <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Admin">Add new role</a> </li> Run the project and you will see the output. Here, You can add a new role. Add a new field in register.cshtml using the following code to assign a role to the user. <div class="form-floating mb-3"> <select asp-for="Input.Role" class="form-control" aria-required="true"> <option value="">Select role</option> @foreach (var item in Model.RoleList) { <option value="@item.Name">@item.Name</option> } </select> <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> </div> To get the list of roles you can add the following code in your register.cshtml.cs file. And Add RoleList = _roleManager.Roles in OnPostAsync method also. public IQueryable<IdentityRole> RoleList { get; set; } public async Task OnGetAsync(string returnUrl = null) { ReturnUrl = returnUrl; RoleList = _roleManager.Roles; ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); } Run the project and see the output. Now, Assign the role to the user, and for that add the following code in OnPostAsync after the user is created. await _userManager.AddToRoleAsync(user, Input.Role); Full code of register.cshtml.cs file. using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; using System.ComponentModel.DataAnnotations; using System.Text; namespace Authorization.Areas.Identity.Pages.Account { public class RegisterModel : PageModel { private readonly SignInManager<IdentityUser> _signInManager; private readonly UserManager<IdentityUser> _userManager; private readonly RoleManager<IdentityRole> _roleManager; private readonly IUserStore<IdentityUser> _userStore; private readonly IUserEmailStore<IdentityUser> _emailStore; private readonly ILogger<RegisterModel> _logger; //private readonly IEmailSender _emailSender; public RegisterModel( UserManager<IdentityUser> userManager, IUserStore<IdentityUser> userStore, SignInManager<IdentityUser> signInManager, ILogger<RegisterModel> logger, RoleManager<IdentityRole> roleManager ) { _userManager = userManager; _userStore = userStore; _emailStore = GetEmailStore(); _signInManager = signInManager; _roleManager = roleManager; _logger = logger; } [BindProperty] public InputModel Input { get; set; } public IQueryable<IdentityRole> RoleList { get; set; } public string ReturnUrl { get; set; } public IList<AuthenticationScheme> ExternalLogins { get; set; } public class InputModel { [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; } [Required] [Display(Name = "Role")] public string Role { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } } public async Task OnGetAsync(string returnUrl = null) { ReturnUrl = returnUrl; RoleList = _roleManager.Roles; ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); } public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); RoleList = _roleManager.Roles; ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = CreateUser(); await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); var result = await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); await _userManager.AddToRoleAsync(user, Input.Role); var userId = await _userManager.GetUserIdAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); if (_userManager.Options.SignIn.RequireConfirmedAccount) { return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl }); } else { await _signInManager.SignInAsync(user, isPersistent: false); return LocalRedirect(returnUrl); } } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } return Page(); } private IdentityUser CreateUser() { try { return Activator.CreateInstance<IdentityUser>(); } catch { throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " + $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " + $"override the register page in /Areas/Identity/Pages/Account/Register.cshtml"); } } private IUserEmailStore<IdentityUser> GetEmailStore() { if (!_userManager.SupportsUserEmail) { throw new NotSupportedException("The default UI requires a user store with email support."); } return (IUserEmailStore<IdentityUser>)_userStore; } } } Now, register one new user and assign a role to them. For example, I have created one user and assign “Admin” to them. I have added two new methods in the controller and added a default view for that. [Authorize(Roles = "User")] public IActionResult UserRoleCheck() { return View(); } [Authorize(Roles = "Admin")] public IActionResult AdminRoleCheck() { return View(); } I have set the Authorize attribute with the role name on both authorization methods. Now, I am running the project and clicking on Admin Role, It will open the page of admin because the logged user role and method role both are the same. If I click on User Role, It will give an Access denied error. Because logged user role and method role both are different. Here, I am using by default access denied page of identity. You can use custom page also, Just set this path to program.cs file. builder.Services.ConfigureApplicationCookie(options => { options.AccessDeniedPath = "/Identity/Account/AccessDenied"; // Customize this path as per your application's structure }); Using this way you will implement the role-based authorization in your application. Conclusion By properly implementing authorization in your applications, you can ensure that resources and sensitive information are accessible only to authorized users. Remember to choose the appropriate authorization technique based on your application’s requirements and complexity.
In this blog, I will explore the top useful C# .NET snippets that every developer should have in their arsenal. From object initialization syntax to dictionary initialization, these snippets cover a wide range of functionalities that will help you streamline your C# development process. Object Initialization Syntax - By using object initialization syntax, you can quickly create and initialize objects without the need for multiple lines of code. public class Product { public string Name { get; set; } public decimal Price { get; set; } } var product = new Product { Name = "Mouse", Price = 999.00 }; Enumerable.Range Method - This snippet simplifies the process of iterating over a range of numbers in a concise and readable manner. foreach (var number in Enumerable.Range(1, 10)) { Console.WriteLine(number); } Conditional Ternary Operator - By using the conditional ternary operator, you can streamline conditional checks and make your code more compact and readable. int time = 7; var result = (time < 5) ? "Weekend" : "Working"; Console.WriteLine(result); Task.WhenAll Method - With Task.WhenAll, you can improve the performance of your asynchronous operations by running them concurrently. async Task DownloadAllAsync(List<string> urls) { var tasks = urls.Select(url => DownloadAsync(url)).ToArray(); await Task.WhenAll(tasks); } async Task DownloadAsync(string url) { Console.WriteLine($"Downloading from {url}"); } Null-Conditional Operator - By using the null-conditional operator, you can handle null values gracefully and prevent runtime exceptions in your code. string firstName = person?.FirstName ?? "Unknown"; Console.WriteLine(firstName); LINQ Query Syntax - By leveraging LINQ query syntax, you can write complex queries on collections with ease and readability. var scores = new int[] { 90, 100, 82, 89, 92 }; var highScores = from score in scores where score >= 90 select score; foreach (var score in highScores) { Console.WriteLine(score); } Using Statement - The using statement is essential for handling disposable objects and preventing resource leaks in your code. using (var streamReader = new StreamReader(@"C:\file.txt")) { string content = streamReader.ReadToEnd(); Console.WriteLine(content); } Expression-Bodied Members - By using expression-bodied members, you can make your code more concise and expressive, especially for simple properties and methods. public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName => $"{FirstName} {LastName}"; // Fullname directly dynamically set at model level. } Dictionary Initialization - Dictionary initialization simplifies the process of populating key-value pairs in a dictionary with a clean and readable syntax. var capitals = new Dictionary<string, string> { ["USA"] = "Washington, D.C.", ["Japan"] = "Tokyo", ["India"] = "Delhi" }; Appending an Element to a List - C# offers numerous methods for adding items to lists. For instance, the widely-used Add() method is available. However, there are plenty of other options as well. Here are five: // Statically defined list List<int> myList = new List<int> {2, 5, 6}; // Appending using Add() myList.Add(5); // [2, 5, 6, 5] // Appending using AddRange() myList.AddRange(new List<int> {9}); // [2, 5, 6, 5, 9] // Appending using Insert() myList.Insert(myList.Count, -4); // [2, 5, 6, 5, 9, -4] // Appending using InsertRange() myList.InsertRange(myList.Count, new List<int> {3}); // [2, 5, 6, 5, 9, -4, 3] // To Check if a List Is Empty List<int> myList = new List<int>(); // Check if a list is empty by its Count if (myList.Count == 0) { // the list is empty } // Check if a list is empty by its type flexibility **preferred method** if (!myList.Any()) { // the list is empty } String Interpolation (Formatting a String) - Oftentimes, we need to format strings to display information in a more readable or structured manner. Here are some options: string name = "Himanshu"; int age = 25; // String formatting using concatenation Console.WriteLine("My name is " + name + ", and I am " + age + " years old."); // String formatting using composite formatting Console.WriteLine("My name is {0}, and I am {1} years old.", name, age); // String formatting using interpolation (C# 6.0+) Console.WriteLine($"My name is {name}, and I am {age} years old"); These are but a small example of the power and flexibility that C# and .NET bring to the table.Thank you for reading! We hope these C# .NET snippets will help you streamline your development process and boost your productivity.
Introduction Logging serves as a cornerstone in the development and deployment life-cycle of any application. Serilog, a renowned logging library for .NET, offers robust features for logging messages to various destinations, including text files. However, deploying applications to IIS servers can introduce challenges, particularly pertaining to permissions issues that impact logging functionality. In this blog post, we'll delve into troubleshooting steps and solutions for addressing Serilog logging challenges in a .NET Core 7 application deployed to an IIS server. Identifying the Issue Upon deploying a .NET Core 7 application utilizing Serilog for logging to an IIS server, you might encounter instances where log messages fail to be written to the designated text file. Upon investigation, it becomes apparent that the application lacks the requisite write permissions for the directory where log files are intended to be stored. This obstacle impedes logging functionality and necessitates corrective measures. Troubleshooting Steps Review Logging Configuration: Commence by scrutinizing the Serilog logging configuration within your .NET Core 7 application. Ensure that the Serilog configuration accurately specifies the file path and logging sink for storing log messages. This step verifies that the logging setup aligns with the requirements of your deployment environment. Inspect File System Permissions: File system permissions are pivotal in enabling applications to write log files. Navigate to the directory designated in your Serilog configuration and examine its permissions settings. Confirm that the IIS application pool identity or the user account running the application possesses adequate permissions to write to the target directory. Grant Write Permissions: In cases where the directory lacks essential write permissions, take action to grant appropriate access rights. Depending on your deployment environment and security policies, you may need to manually adjust permissions or enlist the support of system administrators to ensure proper authorization for the application. Implement Log File Rotation: When log files accumulate rapidly, implementing log file rotation mechanisms can prevent log files from becoming excessively large. Configure Serilog to rotate log files based on size, date, or other criteria to maintain manageable log file sizes and simplify log file management. Validate Logging Functionality: Following the resolution of permissions issues and adjustments to logging configurations, conduct comprehensive testing to verify that log messages are being successfully written to the designated log files. Perform test scenarios that encompass various aspects of your application to ascertain logging functionality under diverse conditions. Conclusion Effective logging is indispensable for diagnosing issues, monitoring application health, and troubleshooting errors in .NET Core 7 applications deployed to IIS servers. By adhering to the troubleshooting steps outlined in this blog post, you can address Serilog logging challenges stemming from permissions constraints and ensure seamless logging functionality in your deployed applications. Regularly reviewing logging configurations and permissions settings is essential to maintaining robust logging capabilities and facilitating efficient application maintenance and troubleshooting.