4: ASP.NET Core Blazor Fundamentals

4: ASP.NET Core Blazor Fundamentals

4: ASP.NET Core Blazor Fundamentals

Imagine building a custom house. Beyond the main structure (which we discussed in Module 3), you need to handle things like how people move between rooms, how specialized tools are made available, how problems are recorded, and where common items are stored. These are the "fundamentals" of making your house truly functional. Blazor fundamentals are similar – they're essential concepts for building robust and maintainable web applications.

1. ASP.NET Core Blazor Routing and Navigation

Analogy: Think of your house having a clear address system (routes) and a helpful host (navigation manager) to guide visitors between rooms.

Routing:

What it is: In Blazor, routing determines which component (think of a "page" or a "view") should be displayed based on the URL in the browser's address bar. It's how Blazor understands "if the user goes to /products, show the ProductList component."

How it works:

  • @page Directive: You define a route directly in a .razor component using the @page directive at the top.
  • @page "/counter"
    
    <h1>Counter</h1>
    <p role="status">Current count: @currentCount</p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
    
    @code {
        private int currentCount = 0;
        private void IncrementCount()
        {
            currentCount++;
        }
    }
    

    In this example, if the browser navigates to /counter, this Counter component will be rendered.

  • Route Parameters: You can include parameters in your route to pass data.
  • @page "/product/{ProductId:int}"
    
    <h3>Product Details for Product ID: @ProductId</h3>
    
    @code {
        [Parameter]
        public int ProductId { get; set; }
    }
    

    Navigating to /product/123 would set ProductId to 123.

  • App.razor: This file contains the Router component, which is responsible for finding the correct component based on the URL. It also defines what to show if a page is found (Found template) or not found (NotFound template).
  • <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
        <NotFound>
            <PageTitle>Not found</PageTitle>
            <LayoutView Layout="@typeof(MainLayout)">
                <p role="alert">Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
    

Navigation:

What it is: Navigation is the programmatic way to move between pages within your Blazor application. It's like your "host" guiding visitors from the living room to the kitchen.

How it works:

  • NavigationManager: Blazor provides a built-in service called NavigationManager. You inject it into your components and use its methods to navigate.
  • @inject NavigationManager NavManager
    
    <button class="btn btn-info" @onclick="GoToHomePage">Go to Home</button>
    <button class="btn btn-success" @onclick="GoToProductDetails">View Product 456</button>
    
    @code {
        private void GoToHomePage()
        {
            NavManager.NavigateTo("/"); // Navigates to the root URL
        }
    
        private void GoToProductDetails()
        {
            NavManager.NavigateTo("/product/456"); // Navigates to a specific product
        }
    }
    
  • NavLink Component: For simple navigation links in your UI (like a menu), Blazor provides the NavLink component, which automatically adds an "active" CSS class when the link matches the current URL.
  • <div class="nav-item px-3">
        <NavLink class="nav-link" href="counter">
            <span class="oi oi-plus" aria-hidden="true"></span> Counter
        </NavLink>
    </div>
    

2. ASP.NET Core Blazor Dependency Injection (DI)

Analogy: Imagine you're building furniture. Instead of you (the component) having to buy all the wood, screws, and tools yourself (creating dependencies directly), you tell a "supplier" (the DI container) what you need. The supplier then provides you with the right materials (services) when you're ready to build. If the supplier changes the type of screw, you don't care, as long as you get screws.

What it is:

Dependency Injection is a design pattern used to achieve Inversion of Control (IoC). Instead of a class creating its dependencies (other objects it needs to function), the dependencies are injected into it by an external entity (the DI container).

Why it's important:

  • Loose Coupling: Components don't directly depend on concrete implementations, making them easier to test and change. (If your furniture builder always asks for "a screw," it doesn't matter if the supplier provides a Philips head or a flathead, as long as it's a screw.)
  • Testability: You can easily swap out real services for "mock" services during testing. (For testing, the supplier might provide fake, lightweight screws.)
  • Reusability: Services can be reused across multiple components.
  • Maintainability: Easier to manage complex applications as dependencies are clearly defined.

How it works:

  • Registering Services (Program.cs): You tell the DI container about the services it can provide. This typically happens in Program.cs.
    • Transient: A new instance is created every time it's requested. (Like borrowing a specific tool each time you need it.)
    • builder.Services.AddTransient<IMyService, MyService>();
      
    • Scoped: A new instance is created once per client request (Blazor Server) or once per user session (Blazor WebAssembly). (Like having a personal tool set for each customer who visits your workshop.)
    • builder.Services.AddScoped<IProductService, ProductService>();
      
    • Singleton: A single instance is created for the entire application lifetime. (Like a shared, central workbench available to everyone.)
    • builder.Services.AddSingleton<ICacheService, CacheService>();
      
  • Injecting Services (.razor components or C# classes): You request the service using the @inject directive in a .razor component or via constructor injection in a C# class.
  • // In MyComponent.razor
    @inject IProductService ProductService
    
    @code {
        protected override async Task OnInitializedAsync()
        {
            var products = await ProductService.GetProductsAsync();
            // ... use products
        }
    }
    
    // In a C# class (e.g., a custom service or component logic)
    public class MyDataService
    {
        private readonly HttpClient _httpClient;
    
        public MyDataService(HttpClient httpClient) // HttpClient is often provided by Blazor's DI
        {
            _httpClient = httpClient;
        }
    
        public async Task<string> GetData()
        {
            return await _httpClient.GetStringAsync("https://api.example.com/data");
        }
    }
    

3. ASP.NET Core Blazor Logging

Analogy: Imagine your house has a "black box" recorder that notes down everything important that happens: "Someone opened the front door," "The light in the kitchen turned on," "There was a strange noise in the attic." This helps you understand what's going on and troubleshoot problems.

What it is:

Logging is the process of recording events and messages from your application's execution. This is crucial for debugging, monitoring, and understanding how your application behaves in different environments.

How it works:

  • ILogger<T> Interface: Blazor (and ASP.NET Core) uses the Microsoft.Extensions.Logging.ILogger<T> interface. You inject an instance of ILogger into your components or services.
  • Log Levels: You can specify different severity levels for your log messages:
    • Trace: Most detailed; rarely enabled in production.
    • Debug: For debugging purposes.
    • Information: General flow of the application.
    • Warning: Unusual or unexpected events that might indicate a problem.
    • Error: Errors that prevent normal operation for a specific activity.
    • Critical: Catastrophic failures.
  • Configuration (appsettings.json): You can configure logging providers (where logs go, e.g., console, debug window, files, external services) and minimum log levels in appsettings.json.
  • {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning" // Log warnings and above for ASP.NET Core
        }
      }
    }
    
  • Using ILogger in Components:
  • @inject ILogger<Counter> Logger // Inject ILogger for this component
    
    <h3>Counter</h3>
    <p role="status">Current count: @currentCount</p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
    
    @code {
        private int currentCount = 0;
    
        private void IncrementCount()
        {
            currentCount++;
            Logger.LogInformation("Counter incremented to {Count}", currentCount); // Log an information message
            if (currentCount > 10)
            {
                Logger.LogWarning("Counter is getting high: {Count}", currentCount); // Log a warning
            }
        }
    }
    
  • Blazor Server vs. WebAssembly Logging:
    • Blazor Server: Logs typically go to the server's configured logging destinations (console, file, etc.), as the code runs on the server.
    • Blazor WebAssembly: Logs by default go to the browser's developer console. You can configure custom logging providers (e.g., to send logs to a server-side API or a logging service) for production environments.

4. ASP.NET Core Blazor Static Files

Analogy: These are the "fixed decorations" or "common supplies" in your house – things like wallpaper, paint, external signs, or standardized utility components that are simply delivered and used as-is, without any complex logic or interaction needed from your house's internal systems.

What it is:

Static files are assets that are directly served to the browser without any server-side processing. These include:

  • HTML files
  • CSS stylesheets (.css)
  • JavaScript files (.js)
  • Images (.png, .jpg, .gif, etc.)
  • Fonts
  • Favicons

How it works:

  • wwwroot Folder: In an ASP.NET Core Blazor project, the wwwroot folder is designated as the web root. Any files placed directly within this folder, or in subfolders within it, are considered static files and are publicly accessible via their URL path.
    • If you have wwwroot/css/site.css, it can be accessed at /css/site.css.
    • If you have wwwroot/images/logo.png, it can be accessed at /images/logo.png.
  • Configuration (Program.cs): The UseStaticFiles() middleware in Program.cs is responsible for serving these files. This is typically configured by default when you create a Blazor project.
  • // Program.cs
    var app = builder.Build();
    // ... other middleware
    
    app.UseStaticFiles(); // This middleware enables serving files from wwwroot
    // ... other middleware and endpoint mapping
    
  • Referencing Static Files in Blazor Components:
  • <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <img src="images/logo.png" alt="Company Logo" />
    
  • Blazor Server vs. WebAssembly: The concept is the same.
    • Blazor Server: The files are served directly by the ASP.NET Core web server.
    • Blazor WebAssembly: The files are downloaded to the client's browser as part of the initial application download. The wwwroot folder defines the root for the client-side served assets.

5. Demo Concept: Understanding Fundamentals Using a Sample App

For the demo, we'll build upon the default Blazor template (either Server or WebAssembly, but WebAssembly is great for showcasing client-side aspects). We'll modify it to highlight each of these fundamental concepts.

Sample App Idea: "My Todo List with Logging & Navigation"

This app will allow users to add, mark complete, and view a list of to-do items. We'll enhance it with our fundamental concepts.

Demo Outline:

  1. Start with a fresh Blazor Project:
    • Create a new Blazor WebAssembly App (or Server, adjust notes as needed).
  2. Routing & Navigation:
    • Basic Page (Pages/Todos.razor): Add @page "/todos" directive. Add a simple heading and a list.
    • NavMenu Link: Modify Shared/NavMenu.razor to include a NavLink to /todos.
    • Programmatic Navigation: Add a button to the Home page (Pages/Index.razor) that navigates to /todos using NavigationManager.
    • Route Parameter: Create Pages/TodoDetails.razor with @page "/todo/{Id:int}". Add a button on the Todos.razor page to navigate to TodoDetails with a specific ID. Show how to display the Id parameter.
  3. Dependency Injection:
    • Create a Service Interface & Implementation: Create Data/ITodoService.cs (interface with methods like GetTodosAsync(), AddTodoAsync(), MarkCompleteAsync()). Create Data/TodoService.cs (implementation that uses an in-memory list for simplicity).
    • Register the Service: In Program.cs, add builder.Services.AddScoped<ITodoService, TodoService>();.
    • Inject and Use the Service: In Pages/Todos.razor, @inject ITodoService TodoService. Use TodoService to fetch and display todos, and to add new ones.
  4. Logging:
    • Inject ILogger: In Pages/Todos.razor, @inject ILogger<Todos> Logger.
    • Add Log Messages: When a new To-do is added, log an Information message. When a To-do is marked complete, log a Debug message. (Optional) Simulate an error (e.g., if a To-do ID is not found for details page) and log an Error message.
    • Show Browser Console: Demonstrate where these logs appear in the browser's developer console (for WebAssembly). For Server, show in the IDE output window.
  5. Static Files:
    • Custom CSS: Create a simple wwwroot/css/custom.css file (e.g., to style completed todos differently). Link this CSS file in wwwroot/index.html (for WebAssembly) or Pages/_Host.cshtml (for Server).
    • Add an Image: Place a small image file (e.g., wwwroot/images/todo-icon.png). Display this image in Pages/Todos.razor using <img src="images/todo-icon.png" alt="Todo Icon" />.
    • Verify Access: Show how to directly access these files via their URL in the browser (e.g., http://localhost:5000/css/custom.css).

Expected Outcome of Demo:

The audience will see a functioning Blazor application where:

  • They can navigate between different pages using both menu links and programmatic buttons.
  • The application's data is managed by a service injected using DI, demonstrating loose coupling.
  • Actions are logged, providing insight into application behavior in the browser console.
  • Custom styling and images are loaded from the wwwroot folder as static assets.

Comments

Popular posts from this blog

Blazor: Building Web Apps with C# - Introduction

Blazor WebAssembly Hosted App Tutorial: Envelope Tracker System

Securing MVC-based Applications Using Blazor