Response Compression in ASP.NET Core 2.1 with React (CRA) - asp.net-core

Here's the official document of doing response compression: https://learn.microsoft.com/en-us/aspnet/core/performance/response-compression?view=aspnetcore-2.1&tabs=aspnetcore2x
I followed the document and found that only while debugging it worked (index.html, bundle.js got compressed). After published, the app didn't compress anything. What should I do to make it work in production.
// Program.cs
public class Program
{
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddAppSettingsFromDockerSecrets();
})
.ConfigureServices(services =>
{
services.AddResponseCompression();
services.AddResponseCaching();
})
.Configure(app =>
{
app.UseResponseCompression();
app.UseResponseCaching();
app.Use(async (context, next) =>
{
context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(5),
};
context.Response.Headers[HeaderNames.Vary] = new string[] { "Accept-Encoding" };
await next();
});
})
.UseStartup<Startup>();
}
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
}
// Startup.cs
public class Startup
{
public Startup(IConfiguration configuration, ILogger<Startup> logger)
{
this.Configuration = configuration;
this.Logger = logger;
}
public IConfiguration Configuration { get; }
public ILogger Logger { get; }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
}

Related

asp.net core web Api integration testing with startup having static file configuration

I was trying to implement integration testing for my api. But, it is saying System.IO.DirectoryNotFoundException : ..WebApi.IntegrationTest\bin\Debug\net6.0\Files\ while I run the test
here is my startup code
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseBlazorFrameworkFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), #"Files")),
RequestPath = new PathString("/Files")
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Initialize(Configuration);
}
when I comment out UseStaticFiles it passes the test else it fails with the above. exception
here is my CustomWebApplicationFactory
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("ApplicationDbContextInMemoryTest");
})
.AddControllers()
.AddApplicationPart(typeof(Startup).Assembly);
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedService = scope.ServiceProvider;
var context = scopedService.GetRequiredService<ApplicationDbContext>();
var logger = scopedService.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
context.Database.EnsureCreated();
try
{
Utilities.InitializeDbForTests(context);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occured seeding the database with test messages, Error: {ex.Message}");
}
}
});
}
protected override IHost CreateHost(IHostBuilder builder)
{
builder.UseContentRoot(Directory.GetCurrentDirectory());
return base.CreateHost(builder);
}
public HttpClient GetAnonymousClient()
{
return CreateClient();
}
}
I tried a lot to fix it but I was not able, please help me, thank you!

.NET Core API Endpoint gives 404 only in deployed version

I am building a .NET Core (3.1) Web API which is being hosted in IIS.
I have 2 endpoints:
/api/status
/api/widget/config/{id}
Both endpoints work perfectly when running locally. The /api/status endpoint works in my deployed version too. But the other endpoint gives a 404 error in the deployed version. As it works locally, I believe this to be an issue with how it is deployed. Please can you help me understand the issue?
Here are my 2 controllers code:
[Route("api/[controller]")]
[ApiController]
public class StatusController : ControllerBase
{
[HttpGet]
public ActionResult Get()
{
return Ok("API is available");
}
}
and
[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
private readonly IWidgetService service;
public WidgetController(IWidgetService _service)
{
service = _service;
}
[HttpGet]
[Route("~/api/[controller]/[action]/{id}")]
public ActionResult Config(Guid id)
{
return Ok(service.GetWidgetConfig(id));
}
}
and below is my Program.cs and Startup.cs:
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
SeedDatabase.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occured seeding the DB");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel();
webBuilder.UseContentRoot(Directory.GetCurrentDirectory());
webBuilder.UseIIS();
webBuilder.UseStartup<Startup>();
});
and
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("sqlConnection"),
options => options.MigrationsAssembly("MyProject")));
services.AddIdentity<ApplicationUser, IdentityRole>(opt =>
{
opt.Password.RequiredLength = 8;
opt.Password.RequireDigit = true;
opt.Password.RequireUppercase = true;
opt.Password.RequireNonAlphanumeric = true;
opt.SignIn.RequireConfirmedAccount = false;
opt.SignIn.RequireConfirmedAccount = false;
opt.SignIn.RequireConfirmedPhoneNumber = false;
}).AddEntityFrameworkStores<ApplicationDbContext>();
services.AddScoped<IWidgetService, WidgetService>();
services.AddCors(o => o.AddPolicy("CorsPolicy", builder => {
builder
.WithMethods("GET", "POST")
.AllowAnyHeader()
.AllowAnyOrigin();
}));
services.AddMvc()
.AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("CorsPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Change your controller code to this:
[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
private readonly IWidgetService service;
public WidgetController(IWidgetService _service)
{
service = _service;
}
[HttpGet("Config/{id}")]
public ActionResult Config(Guid id)
{
return Ok(service.GetWidgetConfig(id));
}
}
Change your code like below:-
Startup.cs
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Controller:-
[ApiController]
[Route("api/[controller]")]
public class WidgetController : ControllerBase
{
private readonly IWidgetService service;
public WidgetController(IWidgetService _service)
{
service = _service;
}
[HttpGet("Config/{id}")]
public ActionResult Config(Guid id)
{
return Ok(service.GetWidgetConfig(id));
}
}
Also try your write connection string in appsettings.Development.json file.
It will resolve your issue.

Error hosting web API in Blazor server web application

We have an existing .NET 5.0 Blazor web application. I have added an ASP.NET Core Web API to the same project because I want to provide a REST interface to external consumers. When I request http://localhost:5000/stripe/customerwebhook, I get a 404 not found error. What could I be missing?
My CustomerWebhookController API class looks like the following:
[Route("stripe/[controller]")]
[ApiController]
public class CustomerWebhookController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Index()
{
return Ok();
}
}
Startup.cs:
using Microsoft.AspNetCore.Mvc;
public partial class Startup
{
public void ConfigureServices(IServiceCollection services)
{
OnConfiguringServices(services);
services.AddHttpContextAccessor();
services.AddScoped<HttpClient>(serviceProvider =>
{
var uriHelper = serviceProvider.GetRequiredService<NavigationManager>();
return new HttpClient
{
BaseAddress = new Uri(uriHelper.BaseUri)
};
});
services.AddHttpClient();
services.AddAuthentication();
services.AddAuthorization();
services.AddControllersWithViews();
services.AddMvc(options => options.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddRazorPages();
services.AddServerSideBlazor().AddHubOptions(o =>
{
o.MaximumReceiveMessageSize = 10 * 1024 * 1024;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationIdentityDbContext identityDbContext)
{
OnConfiguring(app, env);
if (env.IsDevelopment())
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
app.UseDeveloperExceptionPage();
}
else
{
app.Use((ctx, next) =>
{
return next();
});
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseMvcWithDefaultRoute();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}

How to configure dotnetcore 3 site to return 401 with custom authorization filter rather than redirecting to AccessDenied page

I've got a site using the new dotnetcore3 angular template. I've created a custom authorization filter that's currently extremely simple
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly string _claim;
public ClaimRequirementFilter(string claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (_claim != "test")
{
context.Result = new ForbidResult();
}
}
}
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(string claimType) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] {claimType };
}
}
[Route("{jobId}")]
[ClaimRequirement("testfail")]
[HttpGet]
public async Task<IActionResult> GetJob([FromRoute] Guid jobId)
{
//stuff
{
However, whenever a request fails (which will be all of them right now), it 302s me to the AccessDenied page with a returnUrl of the URL I was trying to hit.
However, since this request is being made from my angular client, I would rather it just return a 401 (or 403 since in this case it's because the loggedin user doesn't have permission to do what they're trying to do), and I'm not sure how to configure it.
Per Ruard's request, here is my Startup configuration
public class Startup
{
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry();
services.AddDbContext<ApplicationDbContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
);
services.AddTransient<EmailSender, EmailSender>();
services.AddScoped<IRazorViewToStringRenderer, RazorViewToStringRenderer>();
services.Configure<EmailServiceConfiguration>(Configuration.GetSection("EmailServiceConfiguration"));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = "/auth/login";
options.UserInteraction.LogoutUrl = "/auth/logout";
})
//.AddDeveloperSigningCredential()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
// .AddGoogle(options =>
// {
// IConfigurationSection googleAuthNSection = Configuration.GetSection("Authentication:Google");
// options.ClientId = googleAuthNSection["ClientId"];
// options.ClientSecret = googleAuthNSection["ClientSecret"];
// })
.AddIdentityServerJwt();
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddRazorPages(options =>
{
options.Conventions.AddAreaPageRoute("Identity", "/Identity/Account/Login", "/auth/login");
});
services.AddAuthorization(options =>
{
// options.AddPolicy("RequireAdmin", policy =>
// {
// policy.RequireRole("Admin");
// });
// options.AddPolicy("CreateInternalUsers", policy =>
// {
// // policy.RequireRole("Admin");
// policy.RequireClaim("CreatePPGUser");
// });
});
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationDbContext context, IServiceProvider services)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
context.Database.Migrate();
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}"
);
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
// spa.UseAngularCliServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
}
});
CreateUserRoles(services).Wait();
}
}
You should add a controller that serves as api. From the documentation:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
Methods from this controller will return the StatusCode instead of redirecting the user to a view.
Update
Based on your startup, it seems that you are not setting the CompatibilityVersion:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
As documented, this is required in combination with ApiController:
The preceding change:
Is required to use the [ApiController] attribute at the controller level.
Opts in to potentially breaking behaviors introduced in ASP.NET Core 2.2.
In your case, assuming version 3.0:
services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

ASP.NET Core response compression not working

I have an endpoint that loads a json from a file and then return its contents (ASP.NET Core 2.0):
[Produces("application/json")]
[Route("api/[controller]")]
public class ReportsController : Controller
{
private readonly IHostingEnvironment _hostingEnvironment;
public ReportsController(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
[HttpGet]
public IActionResult Get()
{
try
{
string contentRootPath = System.IO.Path.Combine(_hostingEnvironment.ContentRootPath, "reports");
return Ok(System.IO.File.ReadAllText(contentRootPath + "\\teste.json"));
}
catch (Exception ex)
{
return StatusCode(500, new ResponseError(ex.Message));
}
}
}
When hitting this endpoint in Chrome, I get the following network data:
Now if I enable response compression, the size of the request gets bigger:
Here's my ConfigureServices and Configure methods:
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
});
services.AddApplicationServices();
services.AddDbContext<DatabaseContext>(...);
services.AddCors();
services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<DatabaseContext>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
...
});
services.AddMvc().AddJsonOptions(options => { ... });
services.AddAuthorization(options =>
{
...
});
services.AddEntityFrameworkSqlServer();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseResponseCompression();
app.UseCors(c => c.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
app.UseStaticFiles();
}
What am I missing here?