Web API
74 TopicsCould not load file or assembly System.Data.SqlClient
Hello, I'm currently learning the ASP.NET Core-Web-API (.NET 6) system. I added a class-library (.NET Framework) (v4.8) as a DataAccessLayer to my project. There is a DAO class with a sql-query that uses Dapper and System.Data.SqlClient. just for example: var parameters = new { id }; using (SqlConnection connection = new SqlConnection("...")) { connection.Open(); return connection.ExecuteScalar<bool>(query, parameters); } ASP.NET Core-Web-API - Controller Lib - DAO-Class Now my Problem: When I try to invoke the method which contains this query: [HttpGet("foo")] public ActionResult<bool> Foo(DTO request) { var isOk = Lib.Dao.FetchSomething(request); return Ok(isOk); } , I'll get the following error on line 3: System.IO.FileNotFoundException: "Could not load file or assembly 'System.Data.SqlClient, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified." My lib has the reference to System.Data (4.0.0.0) and System.Data.SqlClient (4.6.1.3). Why do .net tries to search the version 0.0.0.0? Why does this doesn't work? This should work (worked for me on .NET WebForms) Regards.69KViews0likes5CommentsNo Registered Service for IEmailSender
Hello all, I am using ASP.Net Core Web API (.Net Core 😎 to create a web app and API, when i added default Identity and Areas Pages i am getting an error for IEmailSender Service not beeing registered even though i dont need and have registered a mock class for it. Thank you Program.cs using Microsoft.AspNetCore.Identity; using Microsoft.OpenApi.Models; using Microsoft.EntityFrameworkCore; using ProMateAPI.Data; using ProMate.Library; using Swashbuckle.AspNetCore.Filters; using ProMateAPI.Utility; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // Add services to the container. var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); // this would be enabled if we only needed the defaults for users and such //builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) // .AddEntityFrameworkStores<ApplicationDbContext>(); // builder.Services.AddDbContext<TContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("Default"))); builder.Services.AddDbContext<ProMateContext>(); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { In = ParameterLocation.Header, Name = "Authorization", Type = SecuritySchemeType.ApiKey, }); options.OperationFilter<SecurityRequirementsOperationFilter>(); }); /* * options => { options.SignIn.RequireConfirmedAccount = false; options.SignIn.RequireConfirmedAccount = false; } * * */ builder.Services.AddScoped<IEmailSender, MyEmailSender>(); builder.Services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>(); ////builder.Services.AddIdentityApiEndpoints<IdentityUser>().AddEntityFrameworkStores<ApplicationDbContext>(); builder.Services.AddAuthentication(); builder.Services.AddAuthorization(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.MapIdentityApi<IdentityUser>(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapRazorPages(); app.Run(); ------------- MyEmailSender.cs using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using System; using System.Collections.Generic; using System.Linq; using System.Net.Mail; using System.Text; using System.Threading.Tasks; namespace ProMateAPI.Utility; public class MyEmailSender : IEmailSender { public Task SendEmailAsync(string email, string subject, string htmlMessage) { return Task.CompletedTask; } } ----------------- Register.html.cs // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Text.Encodings.Web; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using ProMateAPI.Utility; namespace ProMateAPI.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; // IEmailSender emailSender public RegisterModel( UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, IUserStore<IdentityUser> userStore, SignInManager<IdentityUser> signInManager, ILogger<RegisterModel> logger ) { _userManager = userManager; _roleManager = roleManager; _userStore = userStore; _emailStore = GetEmailStore(); _signInManager = signInManager; _logger = logger; //_emailSender = emailSender; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [BindProperty] public InputModel Input { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public string ReturnUrl { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public IList<AuthenticationScheme> ExternalLogins { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public class InputModel { /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [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; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [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) { // creates missing roles if (!_roleManager.RoleExistsAsync(SD.Roles.Customer.ToString()).GetAwaiter().GetResult()) { foreach (var r in Enum.GetValues(typeof(SD.Roles))) { _roleManager.CreateAsync(new IdentityRole(r.ToString())).GetAwaiter().GetResult(); } } ReturnUrl = returnUrl; ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); } public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); 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."); var userId = await _userManager.GetUserIdAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Page( "/Account/ConfirmEmail", pageHandler: null, values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl }, protocol: Request.Scheme); //await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", // $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); 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); } } // If we got this far, something failed, redisplay form 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; } } }5.3KViews0likes1CommentASP.NET Core 8 Method PATCH is not allowed by Access-Control-Allow-Methods in preflight response.
I created a PATCH method in an ASP.NET Core Web API like this: #region Patch /// <summary> /// Patch TableName by id. /// </summary> // PATCH BY ID: /TableName/patch/{id} [HttpPatch("[action]/{id}", Name = "PatchTableNameById")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [SwaggerOperation( Summary = "Patch Table Name by id", Description = "This can be used to modify any table name record", OperationId = "PatchTableNameById", Tags = new[] { "Patch" } )] public async Task<IActionResult> Patch(string id, [FromBody] JsonPatchDocument<TableName> patchDoc) { if (patchDoc != null) { var tablename = await context.TableName.SingleOrDefaultAsync(x => x.Id.ToString() == id); if (tablename == null) return NotFound(); patchDoc.ApplyTo(tablename); context.Entry(tablename).State = EntityState.Modified; if (!ModelState.IsValid) { return BadRequest(ModelState); } await context.SaveChangesAsync(); return new ObjectResult(tablename); } else { return BadRequest(ModelState); } } This works perfectly fine when I call it using Postman or directly using Swagger. However in my Blazor client I get an error when it is called. I have tried using httpclient, RestSharp and now Flurl. The error returned when using the Developer Tools console to review the log is: Access to fetch at 'http://localhost:5259/SharedServices/Commands/TableName/patch/2bd4e0f3-974a-4794-bb63-b0ce00ba5147' from origin 'http://localhost:5265' has been blocked by CORS policy: Method PATCH is not allowed by Access-Control-Allow-Methods in preflight response. Here is my CORS policy in the Web API. It is called before anything else adding services. builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigins", builder => { builder.WithOrigins("http://localhost:56075", "http://localhost:5265", "https://localhost:7235").AllowAnyHeader().WithMethods("PATCH"); }); }); I have tried various combinations here including of allowing all methods, headers, and origins. In other words opening it wide up which isn't a great security tactic anyways. Here is my client side code for the call. public async Task Patch(string id, JsonPatchDocument<TableName> patchDoc) { var url = "http://localhost:5259/SharedServices/Commands/TableName/patch/" + id; var resultStr = await url.WithHeader("Content-Type", "application/json-patch+json").PatchJsonAsync(patchDoc) .ReceiveString(); } I have tried about every online suggestion to fix this. One problem is there are a lack of issue reports for later versions of asp.net -- post addition of addCORS. I have tried various browsers thinking this maybe a browser issue but no luck there either. Is this possibly a bug in asp.net core cors? I am also using Steeltoe. Is it possible it is interfering with this? Is there any way to turn off a preflight request or modify it? I tried to use various clients such as httpclient, restsharp, flurl. Also tried using a PUT and got the same issue. I have also tried various browsers. The expected result is a successful unblocked call to my web api method. Thanks for any help you can give!4KViews1like2CommentsHttpWebRequest timeout not working
When I disconnect the ethernet, the request just hangs. HttpWebRequest request = (HttpWebRequest )WebRequest.Create(WebAddressPreAmble + WebAddress + WebAddressQuery); // Create(sQuery); request.Credentials = CredentialCache.DefaultCredentials; request.Timeout = 10000; request.ReadWriteTimeout = 1000; // Get the response. HttpWebResponse response = (HttpWebResponse)request.GetResponse(); // Display the status. if (response.StatusDescription.Equals("OK"))3.7KViews1like1CommentManage .NET Microservices APIs with Apache APISIX API Gateway
The API Gateway topic could easily carry several blog posts like this one. In this section, we focus specifically on the usage of https://apisix.apache.org/ for applications developed in https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-6.0 (Assume that you have an API that manages products) and provide an easy example of how to deploy multiple images using https://docs.docker.com/compose/. Now before going to the demo session. Let's discuss first what is API gateway?🤔 API Gateway as a single entry point In today's https://microservices.io/patterns/microservices.html, we usually create multiple microservices for a particular product and the client apps usually need to consume functionality from more than one microservice. And for each of these services, we will have different endpoints accessing these services from the external world it doesn't make sense to expose multiple URLs we should have a single entry point to all our services, and based on the different paths we should be doing the routing as it is shown in the below picture. There are other numerous aspects of an https://wikitech.wikimedia.org/wiki/API_Gateway in building https://dotnet.microsoft.com/en-us/ microservices APIs and web applications. In many scenarios, authentication, security, observability, caching, and transformation are handled centrally. Without an https://apisix.apache.org/docs/apisix/getting-started in place, you might typically implement these concerns for each service, because maintaining them for each service would be a very challenging task and time-consuming. At the same time, you can get the benefit of an https://microservices.io/patterns/apigateway.html in reducing complexity, and delivering high performance for your APIs and it helps you to scale your https://en.wikipedia.org/wiki/Microservices. Prerequisites 👉 To execute and customize the example project per your need shown in this post, here are the minimum requirements you need to install in your system: :right_arrow: https://dotnet.microsoft.com/en-us/download :right_arrow: https://visualstudio.microsoft.com/downloads/ with the Web Development, and/or .NET cross-platform development workload installed. This includes by default .NET 6 development tools. Or https://code.visualstudio.com/. :right_arrow: https://docs.docker.com/desktop/windows/install/ - you need also https://www.docker.com/products/docker-desktop/ installed locally to complete this tutorial. It is available for https://desktop.docker.com/win/edge/Docker%20Desktop%20Installer.exe or https://desktop.docker.com/mac/edge/Docker.dmg. Or install the https://docs.docker.com/engine/context/aci-integration/#install-the-docker-aci-integration-cli-on-linux. Here, is a short summary of what we do: :white_heavy_check_mark: Clone the demo project https://github.com/Boburmirzo/apisix-dotnet-docker from GitHub. :white_heavy_check_mark: Understand the structure of the project and docker-compose.yaml file. :white_heavy_check_mark: Build a multi-container APISIX via Docker CLI. :white_heavy_check_mark: Configure APISIX API Gateway routing for the ASP.NET API. :white_heavy_check_mark: Enable a traffic management plugin. Clone the demo project For this demonstration, we’ll leverage the demo project https://github.com/Boburmirzo/apisix-dotnet-docker I prepared in advance. You can see the complete source code on Github. Use https://git-scm.com/downloads to clone the repository: git clone 'https://github.com/Boburmirzo/apisix-dotnet-docker' Go to root directory of apisix-dotnet-docker cd apisix-dotnet-docker You can open the project in your favorite code editor. I used https://visualstudio.microsoft.com/downloads/. You’ll see the following project directory structure: Understand the structure of the project In the project folders, here is the list of main components you can take a look at: APISIX's config files - All the services are configured by mounting external configuration files in the project onto the docker containers: https://github.com/Boburmirzo/apisix-dotnet-docker/blob/main/apisix_conf/config.yaml defines the configs for apisix. Similarly, configs for etcd, prometheus, and grafana are located in https://github.com/Boburmirzo/apisix-dotnet-docker/blob/main/etcd_conf/etcd.conf.yml, https://github.com/Boburmirzo/apisix-dotnet-docker/blob/main/prometheus_conf/prometheus.yml, and https://github.com/Boburmirzo/apisix-dotnet-docker/tree/main/grafana_conf respectively. ☝️Note that grafana_conf, prometheus_conf, and dashboard_conf are all optional, only if you would like to use them in your project. APISIX's logs - in the https://github.com/Boburmirzo/apisix-dotnet-docker/tree/main/apisix_log folder, APISIX Admin and Control API requests are logged such as access.logor error.log when APISIX is running. ASP.NET WEB API - Next, the https://github.com/Boburmirzo/apisix-dotnet-docker/tree/main/ProductApi folder keeps basically ASP.NET Core application with Controller, Domain, and Service classes). You can also see ProductsController.cs file where there is a simple API to get all products list from the service layer. We will enable routing and Apache APISIX plugins for this endpoint in the next steps. [Route("api/[controller]")] [ApiController] public class ProductsController : ControllerBase { private IProductsService _productsService; public ProductsController(IProductsService productsService) { _productsService = productsService; } [HttpGet] public IActionResult GetAll() { return Ok(_productsService.GetAll()); } } Now let's have a look at a Dockerfile inside the ProductApi folder. It has just a standard Docker file structure and this file is responsible for pulling .NET6 SDK and ASP.NET6 images from docker registry, building and running the applications docker images. FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env WORKDIR /app COPY . ./ RUN dotnet restore "ProductApi.csproj" RUN dotnet publish "ProductApi.csproj" -c Release -o out FROM mcr.microsoft.com/dotnet/aspnet:6.0 ENV TZ=America/Sao_Paulo RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone WORKDIR /app COPY --from=build-env /app/out . ENTRYPOINT ["dotnet", "ProductApi.dll"] Docker compose file - The https://github.com/Boburmirzo/apisix-security-java-spring/blob/main/docker-compose.yml file defines an application with some services: apisix-dashboard - It runs https://apisix.apache.org/docs/dashboard/USER_GUIDE on port 9000. :information: The Dashboard offers another way to interact with APISIX Admin API and we can achieve the same configuration result with the CLI as with the Dashboard. apisix- deploys APISIX and exposes it on port 9080. On your local machine, you can access APISIX Admin API by sending requests to the following URL http://127.0.0.1:9080/ etcd - APISIX uses etcd to save and synchronize configuration. prometheus - APISIX can fetch metrics data about upstream APIs together with https://apisix.apache.org/docs/apisix/plugins/prometheus. grafana - Metrics exported by the [prometheus plugin]https://apisix.apache.org/docs/apisix/plugins/prometheus can be also graphed in Grafana and you can see the running dashboard on port 3000. ProductApi - While deploying the .NET application, docker compose maps port 5555 of the backend service container to port 80 of the host. ☝️You may notice all services are mapped to apisix network. The apisix-dotnet-docker project makes use of the similar example https://github.com/apache/apisix-docker/tree/master/example. Build a multi-container APISIX via Docker CLI Now we can start our application by running docker compose up command from the root folder of the project: docker compose up -d Sample output: [+] Running 7/7 - Network apisix-dotnet-docker_apisix Created 0.0s - Container apisix-dotnet-docker-apisix-dashboard-1 Started 1.2s - Container apisix-dotnet-docker-prometheus-1 Started 0.7s - Container apisix-dotnet-docker-etcd-1 Started 0.9s - Container apisix-dotnet-docker-grafana-1 Started 1.2s - Container apisix-dotnet-docker-productapi-1 Started 0.7s - Container apisix-dotnet-docker-apisix-1 Started 2.0s The running container list you can see by running docker compose ps CLI command or using docker desktop: NAME COMMAND SERVICE STATUS PORTS apisix-dotnet-docker-apisix-1 "sh -c '/usr/bin/api…" apisix running 0.0.0.0:9080->9080/tcp, 0.0.0.0:9091-9092->9091-9092/tcp, 0.0.0.0:9443->9443/tcp apisix-dotnet-docker-apisix-dashboard-1 "/usr/local/apisix-d…" apisix-dashboard running 0.0.0.0:9000->9000/tcp apisix-dotnet-docker-etcd-1 "/opt/bitnami/script…" etcd running 0.0.0.0:12379->2379/tcp apisix-dotnet-docker-grafana-1 "/run.sh" grafana running 0.0.0.0:3000->3000/tcp apisix-dotnet-docker-productapi-1 "dotnet ProductApi.d…" productapi running 0.0.0.0:5555->80/tcp apisix-dotnet-docker-prometheus-1 "/bin/prometheus --c…" prometheus running 0.0.0.0:9090->9090/tcp Once the containers are running, navigate to http://localhost:5555/api/products in your web browser and you will see the following output: Configure APISIX API Gateway When you set up Apache APISIX API Gateway for your APIs, it adds many capabilities including :downwards_button: Routing. Request Aggregation. Authentication. Authorization. Rate Limiting. Caching. Retry policies / QoS. Load Balancing. Logging / Tracing / Metrics. Headers / Method / Query String / gRPC / Claims Transformation. Configuration / Administration REST API. and many more... For more information see the https://apisix.apache.org/docs/. Apache APISIX is based on a couple of primitives: One of them is the https://apisix.apache.org/docs/apisix/terminology/route Another is the https://apisix.apache.org/docs/apisix/terminology/upstream We can create a Route and configure the underlying Upstream. When Apache APISIX receives a request matching the Route, it forwards it to the underlying Upstream. Now let's start with adding a Route and Upstream for the /api/products endpoint. The following command creates a sample Route together with an upstream: curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d ' { "methods": ["GET"], "uri": "/api/products", "upstream": { "type": "roundrobin", "nodes": { "productapi:80": 1 } } }' Once we have created the Route and upstream, we can check whether it works. Apache APISIX should forward the request to our target API /api/products. curl http://127.0.0.1:9080/api/products -i Urraaa👏, yes, it actually does. You can see a sample output: HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Server: APISIX/2.13.1 [{"name":"Macbook Pro","price":1299.9},{"name":"SurfaceBook 3","price":1599.9}] You can also add two or more routes to the API Gateway, let's say one will serve for you Product and another for Customer microservices with the URL path /api/customers. Enable a traffic management plugin With the help of the API Gateway, one can set automatic retries, timeouts, circuit breakers, or rate-limiting for an external upstream API or microservice. Rate limiting is a strategy for limiting network traffic. It puts a cap on how often someone can repeat an action within a specific timeframe – for instance, trying to log into an account. The https://apisix.apache.org/docs/apisix/plugins/limit-count/ :electric_plug: is one among many limiting plugins. It limits the request rate by a fixed number of requests in a given time window. Let’s enable the limit-count plugin on the Route and Upstream. To do so, run the following command: curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "methods": ["GET"], "uri": "/api/products", "plugins": { "limit-count": { "count": 2, "time_window": 60, "rejected_code": 403, "rejected_msg": "Requests are too frequent, please try again later.", "key_type": "var", "key": "remote_addr" } }, "upstream": { "type": "roundrobin", "nodes": { "productapi:80": 1 } } }' The above configuration limits the number of requests to two in 60 seconds. Apache APISIX will handle the first two requests as usual, but a third request in the same period will return a 403 HTTP code: curl http://127.0.0.1:9080/api/products -i Sample output after calling the API 3 times within 60 sec: HTTP/1.1 403 Forbidden Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Server: APISIX/2.13.1 {"error_msg":"Requests are too frequent, please try again later."} Indeed, after reaching the threshold, subsequent requests are not allowed by APISIX. Conclusion We made use of https://github.com/apache/apisix-docker repo where we deployed several components in one run and we demonstrated how the API Gateway can be used to manage sample ASP.NET Core application to retrieve product data using Product microservice's API. Also, you learned how to enable limit-count plugin for the API endpoint. There are many other built-in plugins available in Apache APISIX, you can check them on https://apisix.apache.org/plugins. Basically, APISIX can be a lightweight middleware API Gateway regardless of which programming language, frameworks, tools, or platforms you are developing a microservice or application. Recommended content 💁 ➔ Watch Video Tutorial https://youtu.be/dUOjJkb61so. ➔ Read the blog post https://dev.to/apisix/overview-of-apache-apisix-api-gateway-plugins-2m8o. ➔ Read the blog post https://dev.to/apisix/run-apache-apisix-on-microsoft-azure-container-instance-1gdk. ➔ Read the blog post https://dev.to/apisix/api-security-with-oidc-by-using-apache-apisix-and-microsoft-azure-ad-50h3. ➔ Read the blog post https://dev.to/apisix/apis-observability-with-apache-apisix-plugins-1bnm. Community:right_arrow_curving_down: 🙋 https://apisix.apache.org/docs/general/community :bird: https://twitter.com/ApacheAPISIX :memo: https://join.slack.com/t/the-asf/shared_invite/zt-vlfbf7ch-HkbNHiU_uDlcH_RvaHv9gQ :e_mail: https://email address removed for privacy reasons/ with your questions.3.5KViews0likes0CommentsObject becomes null after calling HTTPClient.PostAsJsonAsync to access Web Api in web form
In my web form (ASP .Net 4.8, VB .Net), I have calling the following function from code-behind submit sales transaction via web api: Public Async Function SendSale(fs As SaleTxnReqModel) As Task(Of SaleTxnRespModel) Dim returnData As SaleTxnRespModel = New SaleTxnRespModel() Dim apiUrl = "Sales/SendSale" Dim resultRemark As String = String.Empty Try Using client = New HttpClient() client.BaseAddress = New Uri(AppComp.NormalizeBaseUrl(constWebApiUri)) client.DefaultRequestHeaders.Accept.Clear() client.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json")) Dim jsonString As String = JsonConvert.SerializeObject(fs) Dim content = New StringContent(jsonString, Encoding.UTF8, "application/json") Dim response As HttpResponseMessage = Await client.PostAsJsonAsync(apiUrl, content) returnData = Await response.Content.ReadAsAsync(Of SaleTxnRespModel)() If Not response.IsSuccessStatusCode Then resultRemark = AppComp.SetWebServStatusText(response.StatusCode) End If End Using Catch ex As Exception resultRemark = AppComp.GetExceptionMessage(ex) Finally returnData.REMARK = resultRemark End Try Return returnData End Function At the receiving web api, the request object has become null. [Route("SendFuelSale")] [HttpPost] public IActionResult SendFuelSale(SaleTxnReqModel txn) { // Process sales transaction... } I have tried using SendAsync method and it worked: Dim jsonString As String = JsonConvert.SerializeObject(fs) Dim content = New StringContent(jsonString, Encoding.UTF8, "application/json") Dim request = New HttpRequestMessage(HttpMethod.Post, apiUrl) request.Content = content Dim response As HttpResponseMessage = Await client.SendAsync(request).ConfigureAwait(False) I just wonder what went wrong when using PostAsJsonAsync method. Any idea? Please adivse. Thanks.3.1KViews0likes2Comments.Net 8 web API with identity Bearer token
I am using .NET 8 Bearer Token not JWT token and I want to check if it is expired from my client app. Is there any way I can decode it or at least check if it is expired or not? Is there a way I can create a service that decode the token or check it is expired or no? Also, how can I know what is the secret key of the token? Program.cs: using EmployeeManagement.Database; using EmployeeManagement.Entities; using EmployeeManagement.Shared.Common; using EmployeeManagement.Shared.Configrations; using EmployeeManagement.Shared.Services.Department; using EmployeeManagement.Shared.Services.Employee; using EmployeeManagement.Shared.Services.UserRole; using EmployeeManagement.Shared.Services.VacationRequests; using FluentValidation; using FluentValidation.AspNetCore; using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.Filters; 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(options => { options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { In = ParameterLocation.Header, Name = "Authorization", Type = SecuritySchemeType.ApiKey }); options.OperationFilter<SecurityRequirementsOperationFilter>(); }); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException( "Connection string Not found"))); builder.Services.AddAuthorization(); builder.Services.AddIdentityApiEndpoints<ApplicationUser>() .AddRoles<IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>(); builder.Services.AddAuthentication().AddJwtBearer(); builder.Services.AddCors(options => { options.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); }); builder.Services.AddAutoMapper( typeof(EmployeeMapperConfig), typeof(UserRoleReMapperConfig), typeof(DepartmentMapperConfig), typeof(VacationRequestsMapperConfig), typeof(ApplicationUserMapperConfig) ); builder.Services.AddScoped<IEmployeeService, EmployeeService>(); builder.Services.AddScoped<IUserRole, UserRoleService>(); builder.Services.AddScoped<IDepartmentService, DepartmentServices>(); builder.Services.AddScoped<IVacationRequestsService, VacationRequestsService>(); builder.Services.AddFluentValidation(); builder.Services.AddValidatorsFromAssemblyContaining<IAssemblyMarker>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.MapIdentityApi<ApplicationUser>(); app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); Angular app: Here I got an exception when I decode the Toke, the exception shows that the token is not in a proper JWT format, because it is Bearer token not a JWT. I want to create my own API and call it here to validate the Token. import { HttpInterceptorFn } from '@angular/common/http'; import { inject } from '@angular/core'; import { jwtDecode } from 'jwt-decode'; import { EmployeeManagementWebAPIService } from '../api/employee-management-web-api.service'; import { RefreshRequest } from '../model/refresh-request'; import { Router } from '@angular/router'; export const tokenInterceptorInterceptor: HttpInterceptorFn = (req, next) => { console.log("tokenInterceptorInterceptor+++"); let authService = inject(EmployeeManagementWebAPIService); let router = inject(Router); const AccessToken = localStorage.getItem('AccessToken'); const RefreshToken = localStorage.getItem('RefreshToken'); if (AccessToken) { console.log("tokenInterceptorInterceptor++999+"+AccessToken); try{ let decodedToken = jwtDecode(AccessToken); console.log("decodedToken+++" + decodedToken); const isExpired = decodedToken && decodedToken.exp ? decodedToken.exp < Date.now() / 1000 : false; if (isExpired) { console.log('token is expired'); const refreshRequest: RefreshRequest = { refreshToken: RefreshToken, }; authService.refreshPost(refreshRequest).subscribe( (newToken: any) => { localStorage.setItem('AccessToken', newToken); req.clone({ setHeaders: { Authorization: `Bearer ${newToken}`, 'Content-Type': 'application/json', // Set content type here }, }); console.log('Refresh token successful:', newToken); }, (error) => { // Handle error response here localStorage.removeItem("AccessToken"); router.navigateByUrl('/login'); console.error('Error refreshing token:', error); } ); } else{ console.error('Token not expired'); } }catch(e){ console.log("invalid token" , e); localStorage.removeItem("AccessToken"); router.navigateByUrl('/login'); } } else{ console.error('Token Not found'); router.navigateByUrl('/login'); } return next(req); }; my login response: { "tokenType": "Bearer", "accessToken": "", "expiresIn": 3600, "refreshToken": "" }2.8KViews0likes1CommentMVC Controller Method Overloading
Hi Folks, The application is built using .NET Framework 4.5.2 with MVC pattern. The current situation requires validating the input parameter and redirecting to the respective method. public ActionResult GetSomeData() { // Default execution for given route } public ActionResult GetSomeData(int inputValue) { // Route detection with the input parameter and change the functional flow for INT data type } public ActionResult GetSomeData(string inputString) { // Route detection with the input parameter and change the functional flow for STRING data type } Is such redirection possible? Thank you for reading.2.6KViews0likes6CommentsAPI Gateway Caching for ASP.NET Core WEB API
Improve performance with caching When you are building an API, you want to keep it simple and fast. Once the concurrent need to read the same data increase, you'll face a few issues😐 where you might be considering introducing caching: :cross_mark: There is latency on some API requests which is noticeably affecting the user's experience. :cross_mark: Fetching data from a database takes more time to respond. :cross_mark: Availability of your API is threatened by the API's high throughput. :cross_mark: There are some network failures in getting frequently accessed information from your API. In this post, we will focus primarily on handling caching at the API Gateway level by using https://apisix.apache.org/docs/apisix/getting-started/ and you will learn how to use proxy-caching plugin to improve response efficiency for https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-6.0 WEB API. Here is an overview of what we cover in this walkthrough: :heavy_check_mark: Caching in API Gateway :heavy_check_mark: About https://apisix.apache.org/docs/apisix/getting-started/ :heavy_check_mark: Run the demo project https://github.com/Boburmirzo/apisix-dotnet-docker :heavy_check_mark: Configure the https://apisix.apache.org/docs/apisix/plugins/proxy-cache/ plugin :heavy_check_mark: Validate Proxy Caching Caching in API Gateway https://en.wikipedia.org/wiki/Cache_(computing) is capable of storing and retrieving network requests and their corresponding responses. Caching happens at different levels in a web application: Edge caching or CDN Database caching Server caching (API caching) Browser caching Reverse Proxy Caching is yet another caching mechanism that is usually implemented inside API Gateway. It can reduce the number of calls made to your endpoint and also improve the latency of requests to your API by caching a response from the upstream. If the API Gateway cache has a fresh copy of the requested resource, it uses that copy to satisfy the request directly instead of making a request to the endpoint. If the cached data is not found, the request travels to the intended upstream services (backend services). Apache APISIX API Gateway https://apisix.apache.org/docs/apisix/getting-started/ is a lightweight, fast, and open-source API Gateway. It can act as a single entry point for all requests into your service network. APISIX can be further extended with built-in plugins to manage network traffic by handling authentication, security, observability, transformation and many more. Also, you can enable API caching with https://apisix.apache.org/docs/apisix/plugins/proxy-cache/ plugin:electric_plug:to cache your API endpoint's responses and enhance the performance. It can be used together with other Plugins too and currently supports disk-based caching. The data to be cached can be filtered with response codes, request modes, or more complex methods using the no_cache and cache_bypass attributes. You can specify cache expiration time or a memory capacity in the plugin configuration as well. Please, refer to other proxy-cache plugin's https://apisix.apache.org/docs/apisix/plugins/proxy-cache/. 🙋🏼 With all this in mind, we'll look next at an example of using proxy-cache plugin offered by Apache APISIX and apply it for ASP.NET Core Web API with a single endpoint. Prerequisites ☝️ If you followed the previous blog post about https://dev.to/apisix/manage-net-microservices-apis-with-apache-apisix-api-gateway-2cbk, make sure you have read it and completed the steps (Run APISIX, etcd and ASP.NET WEB API) before continuing with a demo session. 💁 To execute and customize the example project per your need shown in this post, here are the minimum requirements you need to install in your system: :right_arrow: https://dotnet.microsoft.com/en-us/download :right_arrow: https://visualstudio.microsoft.com/downloads/ with the Web Development, and/or .NET cross-platform development workload installed. This includes by default .NET 6 development tools. Or https://code.visualstudio.com/. :right_arrow: https://docs.docker.com/desktop/windows/install/ - you need also https://www.docker.com/products/docker-desktop/ installed locally to complete this tutorial. It is available for https://desktop.docker.com/win/edge/Docker%20Desktop%20Installer.exe or https://desktop.docker.com/mac/edge/Docker.dmg. Or install the https://docs.docker.com/engine/context/aci-integration/#install-the-docker-aci-integration-cli-on-linux. Run the demo project Until now, I assume that you have the demo project https://github.com/Boburmirzo/apisix-dotnet-docker up and running. You can see the complete source code on Github and the instruction on how to build a multi-container APISIX via Docker CLI. In the ASP.NET Core project, there is a simple API to get all products list from the service layer in https://github.com/Boburmirzo/apisix-dotnet-docker/blob/main/ProductApi/Controllers/ProductsController.cs file. Let's assume that this product list is usually updated only once a day and the endpoint receives repeated billions of requests every day to fetch the product list partially or all of them. In this scenario, using the API caching technique with the proxy-cache plugin might be really helpful🙌. For the demo purpose, we only enable caching for GET method. Ideally, GET requests should be cachable by default - until a special condition arises. Configure the Proxy Cache Plugin Now let's start with adding a proxy-cache plugin to Apache APISIX declarative configuration file config.yaml in the project. Because in the current project, we have not registered yet the plugin we are going to use for this demo. We appended the proxy-cache plugin's name to the end of the plugins list: plugins: - http-logger - ip-restriction … - proxy-cache You can add your cache configuration in the same file if you need to specify values like disk_size, memory_size as shown below: proxy_cache: cache_ttl: 10s # default caching time if the upstream doesn't specify the caching time zones: - name: disk_cache_one # name of the cache. Admin can specify which cache to use in the Admin API by name memory_size: 50m # size of shared memory, used to store the cache index disk_size: 1G # size of disk, used to store the cache data disk_path: "/tmp/disk_cache_one" # path to store the cache data cache_levels: "1:2" # hierarchy levels of the cache Next, we can directly run apisix reload command to reload the latest plugin code without restarting Apache APISIX. See the command to reload the newly added plugin: curl http://127.0.0.1:9080/apisix/admin/plugins/reload -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT Then, we run two more curl commands to configure an Upstream and Route for the /api/products endpoint. The following command creates a sample upstream (that's our API Server): curl "http://127.0.0.1:9080/apisix/admin/upstreams/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d ' { "type": "roundrobin", "nodes": { "productapi:80": 1 } }' Next, we will add a new route with caching ability by setting proxy-cache plugin in plugins property and giving a reference to the upstream service by its unique id to forward requests to the API server: curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d ' { "name": "Route for API Caching", "methods": ["GET"], "uri": "/api/products", "plugins": { "proxy-cache": { "cache_key": ["$uri", "-cache-id"], "cache_bypass": ["$arg_bypass"], "cache_method": ["GET"], "cache_http_status": [200], "hide_cache_headers": true, "no_cache": ["$arg_test"] } }, "upstream_id": 1 }' As you can see in the above configuration, we defined some plugin attributes that we want to cache only successful responses from the GET method of API. Validate Proxy Caching🙎 Finally, we can test the proxy caching if it is working as it is expected. We will send multiple requests to the /api/products path and we should receive HTTP 200 OK response each time. However, the Apisix-Cache-Status in the response shows MISS meaning that the response has not cached yet when the request hits the route for the first time. Now, if you make another request, you will see that you get a cached response with the caching indicator as HIT. Now we can make an initial request: curl http://localhost:9080/api/products -i The response looks like as below: HTTP/1.1 200 OK … Apisix-Cache-Status: MISS When you do the next call to the service, the route responds to the request with a cached response since it has already cached in the previous request: HTTP/1.1 200 OK … Apisix-Cache-Status: HIT Or if you try again to hit the endpoint after the time-to-live (TTL) period for the cache ends, you will get: HTTP/1.1 200 OK … Apisix-Cache-Status: EXPIRED Excellent! We enabled caching for our API endpoint😎 Additional test case 💁🏼 Optionally, you can also add some delay in the Product controller code and measure response time properly with and without cache: [HttpGet] public IActionResult GetAll() { Console.Write("The delay starts.\n"); System.Threading.Thread.Sleep(5000); Console.Write("The delay ends."); return Ok(_productsService.GetAll()); } The curl command to check response time would be: curl -i 'http://localhost:9080/api/products' -s -o /dev/null -w "Response time: %{time_starttransfer} seconds\n" What's next As we learned, it is easy to configure and quick to set up API response caching for our ASP.NET Core WEB API with the help of Apache APISIX. It can reduce significantly the number of calls made to your endpoint and also improve the latency of requests to your API. There are other numerous built-in plugins available in Apache APISIX, you can check them on https://apisix.apache.org/plugins and use them per your need. Recommended content 💁 ➔ Watch Video Tutorial https://youtu.be/dUOjJkb61so. ➔ Read the blog post https://dev.to/apisix/overview-of-apache-apisix-api-gateway-plugins-2m8o. ➔ Read the blog post https://dev.to/apisix/run-apache-apisix-on-microsoft-azure-container-instance-1gdk. ➔ Read the blog post https://dev.to/apisix/api-security-with-oidc-by-using-apache-apisix-and-microsoft-azure-ad-50h3. ➔ Read the blog post https://dev.to/apisix/apis-observability-with-apache-apisix-plugins-1bnm. Community:right_arrow_curving_down: 🙋 https://apisix.apache.org/docs/general/community :bird: https://twitter.com/ApacheAPISIX :memo: https://join.slack.com/t/the-asf/shared_invite/zt-vlfbf7ch-HkbNHiU_uDlcH_RvaHv9gQ :e_mail: https://email address removed for privacy reasons/ with your questions.2.2KViews0likes0CommentsCan I access User Secrets from a _Framework_ Web API project?
I'm targeting .NET Framework 4.8, mainly because I sometimes have to interact with Access databases and I assume I have a better chance of that working for framework than .NET Core. When deployed, I want this API to pick up any credentials it needs from environment variables but I would like to be able to test it locally, so I'm trying to find the best place to store the credentials without checking them in. I have read this and similar articles - user secrets seems like a reasonable solution, maybe there are better ones. https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&tabs=windows&source=docs The part about "Manage User Secrets" in Visual Studio seemed to work at least partially: it installed Microsoft.Configuration.ConfigurationBuilders.UserSecrets and added this to my Web.config: <configSections> <section name="configBuilders" type="System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false" /> </configSections> <configBuilders> <builders> <add name="Secrets" userSecretsId="583724f7-d984-4688-84cd-cb8b28df5527" type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.UserSecrets, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </builders> </configBuilders> It loaded up a secrets.xml file and I can find examples of adding secrets to that. Where I'm stuck is how to access them from the code. I see .NET Core examples where they call AddUserSecrets<Program>() on their ConfigurationBuilder in their Config.cs but again mine is a framework app and I just have a WebApiConfig.cs with a Register(HttpConfiguration config). In there it accesses GlobalConfiguration. I just don't have the past experience to figure out what the analogous thing would be for framework. Thanks, -Jeff2.1KViews0likes2Comments