Access JsonOptions from a middleware in ASP.NET Web Api - c#

In a ASP.NET Web API project I have a custom json converter. It works perfectly fine in a scenario with a typical API controller methods. But somehow I can not access the configured JsonOptions from a middleware which works outside the standard Web API pipeline.
A simple middleware to reproduce the problem:
public sealed class TestMiddleware
{
public async Task Invoke(HttpContext httpContext, IOptions<JsonOptions> jsonOptions)
{
// Some logic to check the middleware must be applied
// ...
// Return a response
await httpContext.Response.WriteAsJsonAsync(data, jsonOptions.Value.SerializerOptions);
}
}
And the configuration fragment:
public void ConfigureServices(IServiceCollection services)
{
services
.AddControllers()
.AddJsonOptions(configure =>
{
configure.JsonSerializerOptions.Converters.Add(new MyCustomConverter());
configure.JsonSerializerOptions.IgnoreNullValues = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseMiddleware<TestMiddlewre>();
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
When a http request hits the middleware IOptions<JsonOptions> jsonOptions has a SerializerOptions object which doesn't contain converters that I have setup in the configuration.
Is there a way to access the actual JsonOptions with the proper configured SerializerOptions?

The problem was quite tricky. There are two namespaces containing a JsonOptions class:
Microsoft.AspNetCore.Mvc
Microsoft.AspNetCore.Http.Json
Using the first one resolved my problem. Internally these two classes are very similar and I didn't pay attention for the corresponding namespace during the middleware implementation.

Related

What is the difference between services.Add and app.Use in startup class in ASP.NET Core?

I begin to learn ASP.NET Core, there, within the framework of the Web API template, there is a Startup class with ConfigureServices() and Configure() methods.
Can anyone tell me about how to use them? I am watching an Udemy course and I didn't understand why the instructor doing
public class Startup
{
private readonly IConfiguration config;
public Startup(IConfiguration config)
{
this.config = config;
}
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.AddApplicationServices(this.config);
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPIv5", Version = "v1" });
});
services.AddCors();
services.AddIdentityServices(this.config);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//if (env.IsDevelopment())
//{
// app.UseDeveloperExceptionPage();
// app.UseSwagger();
// app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebAPIv5 v1"));
//}
app.UseMiddleware<ExceptionMiddleware>();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true) // allow any origin
.AllowCredentials());
services.Add is to register service , app.Use is the way to use Middleware
Configure Service(): It is used to add services to the container and configure those services. basically, service is a component that is intended for common consumption in the application. There is framework service like MVC, EF core, identity so on. but there are also application services that are application-specific like send mail services.
Configure(): it is used to specify how the asp.net core application will respond to individual requests. Through this, we actually build the HTTP request pipeline
public class Startup {
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
// Register services here through dependency injection
}
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env) {
// Add middlware components here
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapGet("/",async Context => {
await context.Response.WriteAsync("Hello World!");
});
});
}
}
Read this example to know more.
Configure.Services and Configure are the starting points from where you can configure framework-level settings in ASP.NET Core.
Configure.Services: To add all the services i.e. the types that you created to add business, database logic to your API. e.g.: You might have created your own logging service and you need to inject in the other classes through DI. To create the objects for these Logging service you need to first register it in the IOC containter of the ASP.NET Core and Configure.Services is the place where you add those services.
Configure: To configure the request-response pipeline i.e. the types that handles the incoming request and outgoing response. e.g.: You might want to add some kind of authentication and authorization before request reaches your controller. Configure is the place where you add those middlewares.

.net core api CORS Get works but cors gets 405

I have the following middleware:
namespace TimeManagement
{
public class CorsMiddleware
{
private readonly RequestDelegate _next;
public CorsMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext)
{
httpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
httpContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
httpContext.Response.Headers.Add("Access-Control-Allow-Headers",
"Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
httpContext.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CorsMiddlewareExtensions
{
public static IApplicationBuilder UseCorsMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CorsMiddleware>();
}
}
}
And the following startup class:
namespace TimeManagement
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
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.AddDbContext<WorkTimeContext>(opt =>
opt.UseInMemoryDatabase("WorkTime"));
services.AddDbContext<TimeManagementContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TimeManagementContext")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseCorsMiddleware();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
}
}
Then I attempt to run https://localhost:5001/api/WorkTimes GET and it returns without issues.
Now I am using an Angular frontend and from there I am trying to post. As you may know, it sends an OPTIONS first and here I get a CORS error:
You need to send status code 200 for CORS preflight request, which your middleware is not setting right now.
Why don't you just use the ASP.NET CORS middleware which handles this?
ASP.NET CORS Middleware
In here ,
https://localhost:5001/api/WorkTimes
is used by the server to return specific data set when valid credentials are sent via a GET HTTP method request.Therefore, in such a scenario, it makes no sense for the server to accept a POST request at that resource/URL, so it may respond with a 405(Method Not Allowed) status code when such a request is made.
The 405(Method Not Allowed) is an HTTP response status code indicating that the specified request HTTP method was received and recognized by the server, but the server has rejected that particular method for the requested resource.A 405 code response confirms that the requested resource is valid and exists, but the client has used an unacceptable HTTP method during the request.
This could happen in a few different circumstances:
The user agent is accidentally sending an incorrect HTTP method.
The server is expecting only a handful of valid HTTP methods for the
requested resource.
Use the ASP.NET CORS middleware as below instead of custom middleware.(Asp.Net Core v3.0 sample code)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseCors(cor=> cor
.AllowAnyHeader()
.WithOrigins("http://localhost:4200","https://localhost:4200"));
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Can you try putting the following in web.config:
<httpHandlers>
...
<add path="*" verb="OPTIONS" type="System.Web.DefaultHttpHandler" validate="true"/>
</httpHandlers>
chrome doesn't support this.
​Access to XMLHttpRequest at '...' from origin '...' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Solution 1 [Temporary Development Solution]
chrome://flags/#out-of-blink-cors
Disable the out-of-blink-cors flag by copying and pasting that address into the address bar of Chrome followed by [enter]. In the page that opens you'll see a highlighted so-called 'flag' (experiment). Disable it and restart Chrome
(Edit: previously I said to enable the flag but only disabling seems to work)
In many cases that solves the issue.
You can upvote this feature on chrome here
Solution 2 (Recommended)
Create your api under in the sub-domain
your api url will be http://localhost:4200/api/YourEndpoint
Here
Voyage is our angular application.
Under it Api where we host our API so which will be under a same domain so CORS policy is not violated.
Cors errors can be very trick. Sometimes the browser returns this erros without really call your api. So first step you need to be sure that the browser call your API. To do this I usually add a dumb inline middleware and put a breakpoint in it. You could add a logger to it too.
The dumb middleware sample:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(async (context, next) =>
{
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
// The others middlewares.
}
Now that you know if the problem is in your browser or in your app you can what can you do?
1) If the problem is in your browser, follow the instruction from #Eldho answer to enable it.
2) If the problem is in your app please read the rest of my answer.
Middlewares are executed in the same sequence you call it in Configure method.
Maybe HttpsRedirection is returning this error. (Big maybe here)
You can try declare your custom app.UseCorsMiddleware() before HttpsRedirection. But I suggest you to use Asp.net Cors Middlewares that already exists and works fine. Why reinvent the wheel?
This is a sample of Asp.Net Core v2.1 Cors Middleware
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("Any",
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("Any");
// The others middlewares.
}
Another approach (only for development) is to use SPA Middleware to forward the requests your SPA. This way your angular app and your Asp.Net app will answer on the same port of localhost and chrome will not block any. But it just work as temporary solution. It is not recommended to use this in production.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("Any");
// The others middlewares.
if (env.IsDevelopment())
{
app.UseSpa(spa =>
{
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
});
}
}

Using Azure Active Directory authentication in ASP.NET Core 2.0 from Web App to Web API

I am trying to pass Azure Active Directory credentials from an ASP.NET Core 2.0 Web App to an ASP.NET Core 2.0 Web API so that the Web API can react based on the user's properties and permissions.
There are, admittedly, quite a few tutorials out there about these technologies in various scenarios and combinations, but I've been having trouble finding clear help specifically for Core 2.0 due to how recently it was released, and I'm avoiding getting too invested in any of the Core 1.x tutorials because it seems there have been some breaking changes when it comes to this (JWT, authentication, etc.). I'm entirely new to Core, so I can't tell what's what.
My goal is to ascertain how this is supposed to be done according to Microsoft's suggestions/standards (if they exist). I want to minimize complexity and make use of the tools that have been designed for this ecosystem.
I have registered both the Web App and the Web API in my Azure Active Directory. When I debug my Web App, I am required to log in via my work/school account with Microsoft, and that is working as expected.
This is all unmodified from what was created as a result of my using the templates/wizards to get started, but for reference:
In the Web App:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// (other unrelated stuff)
app.UseAuthentication();
// (other unrelated stuff)
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddMvc();
}
}
Controllers/HomeController.cs
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
// ****************************************************************
// let's imagine I wanted to call the Web API right here, passing authentication
// and whatnot... what should that look like according to this framework?
// ****************************************************************
return View();
}
// (other unrelated actions)
}
In the Web API:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// (other unrelated stuff)
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
services.AddMvc();
}
}
I can't think of how many queries I threw into Google until finally trying "c# asp core get access token" and getting this very helpful blog as result #3:
https://blogs.msdn.microsoft.com/gianlucb/2017/10/04/access-an-azure-ad-secured-api-with-asp-net-core-2-0/

.NET Core and Swagger API generation

I am creating a barebones .NET Core web api project (Started from blank template below)
https://andrewlock.net/removing-the-mvc-razor-dependencies-from-the-web-api-template-in-asp-net-core/
The code below was working fine, untill I added Swashbuckle.AspNetCore (Along with the configuration code below), now we get this error
InvalidOperationException: No service for type 'Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider' has been registered.
Any ideas?
Please note: We are not using "builder.AppMvc()" as we are trying to slim this api down as much as possible.
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
var builder = services.AddMvcCore();
builder.AddAuthorization();
builder.AddFormatterMappings();
builder.AddJsonFormatters();
builder.AddCors();
// Register the Swagger generator, defining one or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseMvc();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
}
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public IActionResult Get()
{
return Ok(new[] {"value1", "value2"});
}
}
Solution: Use AddMvc() instead of AddMvcCore() in Startup.cs and it will work.
or write:
services.AddMvcCore().AddApiExplorer();
These links can help:
No service for type 'Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory' has been registered
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/299
For ASP.NET Core 2.2 you should write:
services.AddMvcCore().AddApiExplorer();
These types of errors also appear if you try to add AspNetCore MVC controller with Swagger UI into the AspNetCore Web Application. Just separate them into two different projects: mvc controllers and web application.

Add a header to all responses in ASP.NET Core MVC

I would like to know how I can add Access-Control-Allow-Origin:* to my headers.
I've tried this unsuccessfully:
app.Use((context, next) =>
{
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
return next.Invoke();
});
Using app.use(...) and mutating context.Response.Headers from within Startup.Configure is correct, but it's important to do it at the right point in the chain. ASP.NET Core middleware components can "short-circuit" (see the ASP.NET Core Middleware docs), preventing further middleware from being called, and by experimenting with it I've inferred that UseMvc() does so. In an MVC application, then, this means you have to put your app.use(...) call before app.UseMvc().
In other words, starting from the template ASP.NET Core 2.0 application that Visual Studio generates for you, you want to modify Startup.Configure in Startup.cs to look something like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Add header:
app.Use((context, next) =>
{
context.Response.Headers["Access-Control-Allow-Origin"] = "*";
return next.Invoke();
});
app.UseMvc();
}
I tried your code, and it worked beautifully... Placement is key: I'm pretty sure it needs to be early in the chain.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
//app.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.Use((context, next) => {
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
return next.Invoke();
});
app.UseMvc();
app.UseWebSockets();
app.UseSignalR();
}
You can also try to use the in built CORS Middleware in the asp.net core framework rather than creating your own Middleware.
In Configure method of the Startup class add the following code.
// Add CORS for YourAnotherSite
app.UseCors(builder =>
builder.WithOrigins("http://YourAnotherSite.com"));
OR
Use Named Policies
In Configure method of the Startup class
options.AddPolicy("AllowMyOrigin",
builder => builder.WithOrigins("http://YourAnotherSite.com"));
and then in the ConfigureServices method of the startup class.
app.UseCors("AllowMyOrigin");
Alternatively, the Policy can be applied at each Controller or Action methods.
We've found the ApplicationBuilder methods inconsistent too - it's not clear when the handler is handing back to the chain (for instance UseStaticFiles()) and when it's not (UseMvc()).
You don't say what environment you're running under, but if you're intending on using IIS then don't give up on web.config yet! The url rewrite module works perfectly well, and allows you to set outbound rules on all request.
There's a good answer here: https://stackoverflow.com/a/26545975/548664

Categories