ASP.NET Core MVC

 

ASP.NET Core MVC Advanced Topics

Table of Contents

  1. Advanced Routing Techniques
  2. Custom Model Binding
  3. Action Filters and Result Filters
  4. View Components and Partial Views
  5. Interview Questions

Advanced Routing Techniques

Route Templates and Constraints

ASP.NET Core MVC provides powerful routing capabilities that go beyond basic route mapping. Advanced routing techniques allow for complex URL patterns, constraints, and dynamic route generation.

Route Constraints

Route constraints restrict how route parameters are matched, ensuring only valid values are accepted.

[Route("api/products/{id:int:min(1)}")]
public IActionResult GetProduct(int id) { ... }

[Route("api/orders/{orderId:guid}")]
public IActionResult GetOrder(Guid orderId) { ... }

[Route("api/users/{username:alpha:length(3,20)}")]
public IActionResult GetUser(string username) { ... }

Custom Route Constraints

For complex validation requirements, implement custom constraints.

public class DateRouteConstraint : IRouteConstraint
{
    public bool Match(HttpContext httpContext, IRouter route, string routeKey,
                      RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var value))
            return false;

        return DateTime.TryParse(value?.ToString(), out _);
    }
}

// Register in Program.cs
builder.Services.Configure<RouteOptions>(options =>
{
    options.ConstraintMap.Add("date", typeof(DateRouteConstraint));
});

// Usage
[Route("api/events/{date:date}")]
public IActionResult GetEventsByDate(DateTime date) { ... }

Attribute Routing with Complex Patterns

[Route("api/[controller]")]
public class ProductsController : Controller
{
    [HttpGet("{id:int}")]
    public IActionResult GetById(int id) { ... }

    [HttpGet("category/{category}/price/{minPrice:decimal}-{maxPrice:decimal}")]
    public IActionResult GetByCategoryAndPrice(string category, decimal minPrice, decimal maxPrice) { ... }

    [HttpGet("search/{query?}")]
    public IActionResult Search(string query = null) { ... }
}

Route Areas

Areas organize large MVC applications into functional groups.

[Area("Admin")]
[Route("admin/[controller]")]
public class UsersController : Controller
{
    [HttpGet]
    public IActionResult Index() { ... }
}

// In Program.cs
app.MapControllerRoute(
    name: "areas",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Dynamic Route Generation

public class RouteGenerator
{
    private readonly LinkGenerator _linkGenerator;

    public RouteGenerator(LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public string GenerateProductUrl(int productId, HttpContext context)
    {
        return _linkGenerator.GetUriByAction(
            context,
            action: "Details",
            controller: "Products",
            values: new { id = productId });
    }
}

Route Precedence and Ordering

Routes are evaluated in the order they're registered. More specific routes should be registered before general ones.

// Program.cs
app.UseRouting();

app.MapControllerRoute(
    name: "product-details",
    pattern: "products/{id:int}",
    defaults: new { controller = "Products", action = "Details" });

app.MapControllerRoute(
    name: "product-category",
    pattern: "products/{category}",
    defaults: new { controller = "Products", action = "Category" });

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Advanced Routing Best Practices

  1. Use Attribute Routing for complex applications
  2. Apply Constraints to prevent invalid parameter binding
  3. Organize with Areas for large applications
  4. Test Routes thoroughly to avoid conflicts
  5. Use LinkGenerator for dynamic URL generation
  6. Consider SEO when designing route patterns

Custom Model Binding

Understanding Model Binding

Model binding in ASP.NET Core MVC automatically maps HTTP request data to action method parameters and model properties. Custom model binding allows handling complex scenarios that built-in binders can't handle.

Custom Model Binder Implementation

public class CustomDateTimeBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        var value = valueProviderResult.FirstValue;

        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        // Custom parsing logic
        if (DateTime.TryParseExact(value, "yyyy-MM-ddTHH:mm:ssZ",
            CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var dateTime))
        {
            bindingContext.Result = ModelBindingResult.Success(dateTime);
        }
        else
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName,
                "Invalid date format. Expected: yyyy-MM-ddTHH:mm:ssZ");
        }

        return Task.CompletedTask;
    }
}

Custom Model Binder Provider

public class CustomDateTimeBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType == typeof(DateTime) &&
            context.Metadata.ContainerType == typeof(CustomModel))
        {
            return new CustomDateTimeBinder();
        }

        return null;
    }
}

// Register in Program.cs
builder.Services.AddControllers(options =>
{
    options.ModelBinderProviders.Insert(0, new CustomDateTimeBinderProvider());
});

Complex Object Model Binding

public class ProductSearchModel
{
    public string Name { get; set; }
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }
    public List<string> Categories { get; set; }
    public SortOrder SortBy { get; set; }
}

public class ProductSearchBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var model = new ProductSearchModel();

        // Bind simple properties
        model.Name = bindingContext.ValueProvider.GetValue("name").FirstValue;

        // Bind complex properties
        var minPriceValue = bindingContext.ValueProvider.GetValue("minPrice").FirstValue;
        if (decimal.TryParse(minPriceValue, out var minPrice))
            model.MinPrice = minPrice;

        // Bind collections
        var categories = bindingContext.ValueProvider.GetValue("categories");
        if (categories != ValueProviderResult.None)
        {
            model.Categories = categories.ToList();
        }

        // Bind enums
        var sortByValue = bindingContext.ValueProvider.GetValue("sortBy").FirstValue;
        if (Enum.TryParse<SortOrder>(sortByValue, true, out var sortOrder))
            model.SortBy = sortOrder;

        bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;
    }
}

Model Binding with Validation

public class ValidatedModelBinder : IModelBinder
{
    private readonly IModelBinder _fallbackBinder;

    public ValidatedModelBinder(IModelBinder fallbackBinder)
    {
        _fallbackBinder = fallbackBinder;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        await _fallbackBinder.BindModelAsync(bindingContext);

        if (bindingContext.Result.IsModelSet)
        {
            var model = bindingContext.Result.Model as IValidatableObject;
            if (model != null)
            {
                var validationResults = new List<ValidationResult>();
                var validationContext = new ValidationContext(model);

                if (!Validator.TryValidateObject(model, validationContext, validationResults, true))
                {
                    foreach (var validationResult in validationResults)
                    {
                        foreach (var memberName in validationResult.MemberNames)
                        {
                            bindingContext.ModelState.AddModelError(memberName, validationResult.ErrorMessage);
                        }
                    }
                }
            }
        }
    }
}

Custom Value Providers

public class HeaderValueProvider : IValueProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public HeaderValueProvider(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public bool ContainsPrefix(string prefix)
    {
        return _httpContextAccessor.HttpContext.Request.Headers.ContainsKey(prefix);
    }

    public ValueProviderResult GetValue(string key)
    {
        var headerValue = _httpContextAccessor.HttpContext.Request.Headers[key];
        return headerValue.Count > 0
            ? new ValueProviderResult(headerValue.ToString())
            : ValueProviderResult.None;
    }
}

Model Binding Best Practices

  1. Use Built-in Binders when possible
  2. Implement Custom Binders for complex scenarios
  3. Validate Input in custom binders
  4. Handle Errors Gracefully with meaningful messages
  5. Test Binders thoroughly
  6. Consider Performance implications

Action Filters and Result Filters

Understanding Filters

Filters in ASP.NET Core MVC allow running code before or after action method execution. They enable cross-cutting concerns like logging, caching, and authorization.

Types of Filters

  • Authorization Filters: Run first, determine if user is authorized
  • Resource Filters: Run after authorization, before model binding
  • Action Filters: Run before and after action method execution
  • Exception Filters: Handle exceptions thrown by action methods
  • Result Filters: Run before and after action result execution

Custom Action Filter

public class LoggingActionFilter : IActionFilter
{
    private readonly ILogger<LoggingActionFilter> _logger;

    public LoggingActionFilter(ILogger<LoggingActionFilter> logger)
    {
        _logger = logger;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        _logger.LogInformation($"Action {context.ActionDescriptor.DisplayName} executing");
        _logger.LogInformation($"Parameters: {JsonSerializer.Serialize(context.ActionArguments)}");
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.Exception != null)
        {
            _logger.LogError(context.Exception, $"Action {context.ActionDescriptor.DisplayName} failed");
        }
        else
        {
            _logger.LogInformation($"Action {context.ActionDescriptor.DisplayName} executed successfully");
        }
    }
}

Async Action Filter

public class AsyncLoggingActionFilter : IAsyncActionFilter
{
    private readonly ILogger<AsyncLoggingActionFilter> _logger;

    public AsyncLoggingActionFilter(ILogger<AsyncLoggingActionFilter> logger)
    {
        _logger = logger;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        _logger.LogInformation($"Action {context.ActionDescriptor.DisplayName} starting");

        var executedContext = await next();

        if (executedContext.Exception != null)
        {
            _logger.LogError(executedContext.Exception, $"Action failed");
        }
        else
        {
            _logger.LogInformation($"Action completed in {DateTime.UtcNow - context.HttpContext.Items["StartTime"] as TimeSpan?}");
        }
    }
}

Custom Result Filter

public class CustomResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is ViewResult viewResult)
        {
            // Modify view data before rendering
            viewResult.ViewData["CustomData"] = "Added by filter";
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Cleanup or logging after result execution
        context.HttpContext.Response.Headers.Add("X-Custom-Header", "Processed");
    }
}

Exception Filter

public class GlobalExceptionFilter : IExceptionFilter
{
    private readonly ILogger<GlobalExceptionFilter> _logger;

    public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
    {
        _logger = logger;
    }

    public void OnException(ExceptionContext context)
    {
        _logger.LogError(context.Exception, "Unhandled exception occurred");

        if (context.Exception is ArgumentException)
        {
            context.Result = new BadRequestObjectResult(new
            {
                Error = "Invalid argument",
                Message = context.Exception.Message
            });
            context.ExceptionHandled = true;
        }
        else if (!context.ExceptionHandled)
        {
            context.Result = new ObjectResult(new
            {
                Error = "Internal server error",
                Message = "An unexpected error occurred"
            })
            {
                StatusCode = 500
            };
            context.ExceptionHandled = true;
        }
    }
}

Filter Ordering and Scope

[TypeFilter(typeof(LoggingActionFilter), Order = 1)]
[ServiceFilter(typeof(AuthorizationFilter), Order = 2)]
public class ProductsController : Controller
{
    [HttpGet]
    [ValidateAntiForgeryToken]
    [ResponseCache(Duration = 300)]
    public IActionResult Index()
    {
        return View();
    }
}

Global Filters Registration

// Program.cs
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalExceptionFilter>();
    options.Filters.Add<LoggingActionFilter>();
});

Custom Filter Attributes

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
    private readonly string _requiredRole;

    public CustomAuthorizeAttribute(string requiredRole)
    {
        _requiredRole = requiredRole;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        if (!user.IsInRole(_requiredRole))
        {
            context.Result = new ForbidResult();
        }
    }
}

Filters Best Practices

  1. Use Appropriate Filter Types for different concerns
  2. Order Filters Carefully to ensure proper execution
  3. Handle Exceptions in exception filters
  4. Avoid Complex Logic in filters
  5. Test Filters in isolation
  6. Use Dependency Injection in filters

View Components and Partial Views

Understanding View Components

View components are reusable UI components that render a portion of the response independently of the action method. They're similar to partial views but with their own logic.

Creating a View Component

public class NavigationMenuViewComponent : ViewComponent
{
    private readonly INavigationService _navigationService;

    public NavigationMenuViewComponent(INavigationService navigationService)
    {
        _navigationService = navigationService;
    }

    public async Task<IViewComponentResult> InvokeAsync(string currentSection)
    {
        var menuItems = await _navigationService.GetMenuItemsAsync(User.Identity.Name);
        var model = new NavigationMenuViewModel
        {
            MenuItems = menuItems,
            CurrentSection = currentSection
        };

        return View(model);
    }
}

View Component View

@model NavigationMenuViewModel

<nav class="navbar">
    <ul class="nav">
        @foreach (var item in Model.MenuItems)
        {
            <li class="@(item.Section == Model.CurrentSection ? "active" : "")">
                <a asp-controller="@item.Controller" asp-action="@item.Action">@item.Text</a>
            </li>
        }
    </ul>
</nav>

Invoking View Components

<!-- In a view -->
@await Component.InvokeAsync("NavigationMenu", new { currentSection = "Home" })

<!-- Or with strongly-typed invocation -->
@await Component.InvokeAsync<NavigationMenuViewComponent>(new { currentSection = "Home" })

Asynchronous View Components

public class RecentPostsViewComponent : ViewComponent
{
    private readonly IBlogService _blogService;

    public RecentPostsViewComponent(IBlogService blogService)
    {
        _blogService = blogService;
    }

    public async Task<IViewComponentResult> InvokeAsync(int count = 5)
    {
        var posts = await _blogService.GetRecentPostsAsync(count);
        return View(posts);
    }
}

View Component Discovery

View components are discovered by:

  1. Looking for classes ending with "ViewComponent"
  2. Classes implementing ViewComponent
  3. Classes in the "Components" folder

Complex View Components

public class ProductFilterViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(ProductFilterOptions options)
    {
        // Complex filtering logic
        var filterModel = new ProductFilterViewModel
        {
            Categories = GetCategories(),
            PriceRanges = GetPriceRanges(),
            Brands = GetBrands(),
            SelectedOptions = options
        };

        return View(filterModel);
    }

    private IEnumerable<SelectListItem> GetCategories()
    {
        // Implementation
        return new List<SelectListItem>();
    }

    // Other helper methods...
}

Partial Views

Partial views are reusable view fragments that can be rendered within other views.

Creating Partial Views

<!-- _ProductCard.cshtml -->
@model Product

<div class="product-card">
    <h3>@Model.Name</h3>
    <p>@Model.Description</p>
    <span class="price">@Model.Price.ToString("C")</span>
    <a asp-action="Details" asp-route-id="@Model.Id" class="btn btn-primary">View Details</a>
</div>

Rendering Partial Views

<!-- Synchronous rendering -->
@Html.Partial("_ProductCard", product)

<!-- Asynchronous rendering -->
@await Html.PartialAsync("_ProductCard", product)

<!-- With model -->
@Html.Partial("_ProductCard", new Product { Name = "Test", Price = 10.99M })

Partial View with ViewData

<!-- In main view -->
@{
    ViewData["Title"] = "Product List";
    ViewData["ShowEditButton"] = User.IsInRole("Admin");
}

@Html.Partial("_ProductList", products)

<!-- In partial view -->
@model IEnumerable<Product>

<h2>@ViewData["Title"]</h2>

@foreach (var product in Model)
{
    <div class="product">
        <h3>@product.Name</h3>
        @if ((bool?)ViewData["ShowEditButton"] == true)
        {
            <a asp-action="Edit" asp-route-id="@product.Id">Edit</a>
        }
    </div>
}

Partial Views vs View Components

Use Partial Views when:

  • Simple UI rendering without complex logic
  • Sharing common UI elements
  • No dependency injection needed

Use View Components when:

  • Complex rendering logic required
  • Need dependency injection
  • Asynchronous operations needed
  • Reusable across different controllers

Advanced View Component Techniques

View Component with Multiple Views

public class AlertViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(AlertType type, string message)
    {
        var model = new AlertViewModel { Type = type, Message = message };

        switch (type)
        {
            case AlertType.Success:
                return View("Success", model);
            case AlertType.Warning:
                return View("Warning", model);
            case AlertType.Error:
                return View("Error", model);
            default:
                return View("Info", model);
        }
    }
}

View Component Caching

[ViewComponentCache(Duration = 300)]
public class WeatherViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(string location)
    {
        var weather = await GetWeatherAsync(location);
        return View(weather);
    }
}

View Component in Layout

<!-- _Layout.cshtml -->
<!DOCTYPE html>
<html>
<head>
    <title>@ViewData["Title"]</title>
</head>
<body>
    @await Component.InvokeAsync("NavigationMenu")
    
    <main>
        @RenderBody()
    </main>
    
    @await Component.InvokeAsync("Footer")
</body>
</html>

Best Practices for View Components and Partial Views

  1. Use View Components for complex, reusable UI logic
  2. Use Partial Views for simple UI fragments
  3. Keep Components Focused on single responsibility
  4. Test View Components like controllers
  5. Cache When Appropriate to improve performance
  6. Use Async Methods for I/O operations

Interview Questions

Basic Level (1-2 years experience)

Q1. What is routing in ASP.NET Core MVC?

Answer: Routing in ASP.NET Core MVC is the process of mapping incoming HTTP requests to specific controller actions. It uses route templates to define URL patterns and can include parameters, constraints, and defaults. Routes can be defined using conventional routing or attribute routing.

Q2. What is model binding in MVC?

Answer: Model binding is the process by which ASP.NET Core MVC maps HTTP request data to action method parameters. It automatically converts form data, query strings, route parameters, and JSON/XML bodies into .NET objects.

Q3. What are action filters?

Answer: Action filters are attributes that allow running code before or after action method execution. They enable cross-cutting concerns like logging, caching, and validation. Types include authorization filters, action filters, result filters, and exception filters.

Q4. What is the difference between a partial view and a view component?

Answer: Partial views are simple reusable view fragments for rendering UI without logic. View components are more powerful, supporting dependency injection, async operations, and their own logic in a class that implements ViewComponent.

Q5. How do you pass data to a partial view?

Answer: Data can be passed to partial views using the model parameter in Html.Partial/PartialAsync, or through ViewData/ViewBag. The partial view can access the parent's ViewData and can receive its own model.

Intermediate Level (2-3 years experience)

Q6. How do you implement custom model binding?

Answer: Implement IModelBinder or IModelBinderProvider. Create a class that implements IModelBinder with BindModelAsync method. For complex scenarios, implement IModelBinderProvider to return appropriate binders based on context. Register the provider in Startup.cs.

Q7. What are the different types of filters and their execution order?

Answer:

  1. Authorization filters (first)
  2. Resource filters
  3. Action filters (before action execution)
  4. Exception filters (on exceptions)
  5. Result filters (before/after result execution)

Q8. How do you create a custom route constraint?

Answer: Implement IRouteConstraint interface with a Match method that validates route parameters. Register the constraint in RouteOptions.ConstraintMap in Startup.cs. Use the constraint name in route templates.

Q9. What are view components used for?

Answer: View components render reusable UI portions independently of controller actions. They're useful for complex widgets, sidebars, navigation menus, or any UI element that requires its own logic and data access.

Q10. How do you handle exceptions in MVC applications?

Answer: Use exception filters to catch and handle exceptions globally. Implement IExceptionFilter or create custom exception handling middleware. For controller-specific handling, use try-catch blocks or override OnException in controllers.

Advanced Level (3+ years experience)

Q11. How would you implement a complex routing strategy for a large application?

Answer: Use attribute routing with areas for organization. Implement custom route constraints for validation. Use route precedence carefully. Consider SEO implications. Implement dynamic route generation with LinkGenerator. Use conventional routing for simple cases and attribute routing for complex scenarios.

Q12. What are the performance implications of different model binding approaches?

Answer: Built-in binders are optimized and should be used when possible. Custom binders add overhead but allow complex scenarios. Avoid complex validation in binders - use separate validation. Consider caching for expensive binding operations. Test binding performance under load.

Q13. How do you implement global error handling with filters?

Answer: Create a custom exception filter implementing IExceptionFilter. Register it globally in Startup.cs. Handle different exception types appropriately. Log errors and return user-friendly responses. Consider different error responses for API vs MVC requests.

Q14. What strategies do you use for organizing view components in large applications?

Answer: Group related components in folders. Use naming conventions. Implement base classes for common functionality. Use dependency injection for services. Consider creating component libraries. Test components in isolation.

Q15. How do you implement caching with action filters?

Answer: Create custom action filters that check cache before action execution and store results after. Use IDistributedCache for scalability. Implement cache invalidation strategies. Consider cache keys carefully. Handle concurrent requests properly.

Q16. What are the challenges of custom model binding and how do you mitigate them?

Answer: Challenges include complexity, performance, testing, and maintainability. Mitigate by keeping binders simple, thorough testing, proper error handling, and documentation. Use built-in binders when possible and only customize when necessary.

Q17. How do you implement dynamic view component rendering?

Answer: Use Component.InvokeAsync with dynamic component names. Implement a component resolver service. Cache component results. Handle missing components gracefully. Consider security implications of dynamic rendering.

Q18. What are the best practices for filter ordering and dependencies?

Answer: Order filters by specificity and dependency. Authorization first, then resource filters, action filters, etc. Use Order property for fine control. Consider filter dependencies and ensure required services are available. Test filter combinations.

Q19. How do you implement conditional routing based on runtime conditions?

Answer: Use custom route constraints that check runtime state. Implement routing middleware for complex scenarios. Use feature flags for conditional routes. Consider performance implications of runtime checks.

Q20. How do you test custom filters and model binders?

Answer: Unit test filters and binders in isolation. Mock dependencies. Test different scenarios including error cases. Integration test with controllers. Use TestServer for end-to-end testing. Consider performance testing for custom implementations.


Additional Interview Tips

MVC Best Practices

  • Use Attribute Routing for complex applications
  • Implement Custom Filters for cross-cutting concerns
  • Keep Controllers Thin with business logic in services
  • Use View Models to avoid over-posting
  • Validate Input on both client and server
  • Handle Errors Gracefully with appropriate user feedback

Common Anti-Patterns to Avoid

  • Fat Controllers: Move logic to services
  • Magic Strings: Use constants for action names and routes
  • Tight Coupling: Use dependency injection
  • Ignoring Security: Always validate and authorize
  • Poor Error Handling: Don't expose sensitive information
  • Inconsistent Naming: Follow conventions

Testing Strategies

  • Unit Tests: Test controllers, filters, and binders in isolation
  • Integration Tests: Test complete request/response cycles
  • UI Tests: Test views and components with Selenium
  • Performance Tests: Load test critical paths
  • Security Tests: Test authorization and input validation

Performance Considerations

  • Cache Views: Use output caching for expensive views
  • Optimize Bundling: Minimize and bundle CSS/JS
  • Use CDN: For static assets
  • Database Optimization: Efficient queries and indexing
  • Async Operations: Use async/await for I/O
  • Profiling: Use tools to identify bottlenecks

Security Best Practices

  • Validate Input: Prevent XSS and injection attacks
  • Use Anti-Forgery Tokens: For form posts
  • Implement Authorization: At controller and action levels
  • Secure Configuration: Don't hardcode secrets
  • Log Security Events: Monitor suspicious activity
  • Regular Updates: Keep dependencies updated

Comments

Popular posts from this blog