I am building an asp.net core Web API and I need to be able to hide some of the actions in a controller.
I use the following code to return HTTP 404 (Not Found):
[HttpGet]
public IActionResult Index()
{
if(!_isEnabled)
{
return NotFound();
}
However, in my browser I get this result:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
"title": "Not Found",
"status": 404,
"traceId": "00-502319d62a6027718d2ee2cb3c9f263f-28de7bfdfb48f2d8-00"
}
I need to make the call as if the controller does not exists and the browser shows this:
How can a Controller returns a "real" HTTP 404 experience as if the controller dos not exists at that route?
Update 1
The answers return a JSON data and response code 404.
I am trying to do something different.
I am trying to hide the controller as if it doesn't exist for security reasons. I like the end user browser see above screenshot (Edge in my example)
Update 2
I changed to the following code:
[HttpGet]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult Index()
{
if(!_isEnabled)
{
return StatusCode(StatusCodes.Status404NotFound);
}
and the controller returns the following result:
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.4","title":"Not Found","status":404,"traceId":"00-3275026575270e11a4b1a5ab0817776a-a4777e626460faeb-00"}
The behavior is strange. Is it a new feature in aspnet code 6 ?
Update 3
Here is my middleware setup in the Program.c. It is plain oob setup:
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddAzureWebAppDiagnostics();
// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddApplicationInsightsTelemetry();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
Solution For Update 1:
Middleware could be your savior here thus can be achived what you are trying to implement.
Controller:
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
// return Ok(NotFound());
return StatusCode(StatusCodes.Status404NotFound);
}
Note: You can choose either of the status pattern.
Middleware:
public class CustomResponseMiddleware
{
private readonly RequestDelegate _next;
public CustomResponseMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
if (httpContext.Response.StatusCode == 404)
{
httpContext.Response.Redirect("/WrongControllerName/WrongAction");
}
await _next(httpContext);
}
}
Note: As you can see, we are checking the controller status code and checking if any 404 decteced. Once the desired status code we will redirect a controller which doesn't exist at all that eventually generate the expected output.
Register Middleware In Program.cs:
app.UseMiddleware<CustomResponseMiddleware>();
Output:
Related
I have tried to configure swagger in my asp.net core api and getting the following error. Failed to load API definition undefined /swagger/v1/swagger.json
I am not sure why I am getting this error. I have added the necessary configuration in the startup file
I have tried the following paths but there has been no difference
/swagger/v1/swagger.json
../swagger/v1/swagger.json
v1/swagger.json
startup.cs
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSwaggerGen(c =>
{
});
services.AddDbContext<NorthwindContext>(item => item.UseSqlServer(Configuration.GetConnectionString("NorthwindDBConnection")));
services.AddCors(option => option.AddPolicy("MyPolicy", builder => {
builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
}));
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddScoped<ICustomerRepository, CustomerRepository>();
}
// 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
{
// 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.UseCors("MyPolicy");
app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API name"); });
app.UseMvc();
}
}
CustomerController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Customer.Repository;
using CustomerService.Models;
using CustomerService.ViewModel;
using Microsoft.AspNetCore.Mvc;
namespace CustomerService.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CustomersController : Controller
{
ICustomerRepository _customersRepository;
public CustomersController(ICustomerRepository customersRepository)
{
_customersRepository = customersRepository;
}
[HttpGet]
[Route("GetCustomers")]
//[NoCache]
[ProducesResponseType(typeof(List<CustomerViewModel>), 200)]
[ProducesResponseType(typeof(ApiResponse), 400)]
public async Task<IActionResult> Customers()
{
try
{
var customers = await _customersRepository.GetAllCustomers();
if (customers == null)
{
return NotFound();
}
return Ok(customers);
}
catch
{
return BadRequest();
}
}
[HttpGet]
[Route("GetCustomer")]
//[NoCache]
[ProducesResponseType(typeof(List<CustomerViewModel>), 200)]
[ProducesResponseType(typeof(ApiResponse), 400)]
public async Task<IActionResult> Customers(string customerId)
{
if (customerId == null)
{
return BadRequest();
}
try
{
var customer = await _customersRepository.GetCustomer(customerId);
if (customer == null)
{
return NotFound();
}
return Ok(customer);
}
catch
{
return BadRequest();
}
}
[HttpPost]
[Route("AddCustomer")]
public async Task<IActionResult> AddCustomer([FromBody] CustomerViewModel model)
{
if (ModelState.IsValid)
{
try
{
var customerId = await _customersRepository.Add(model);
if (customerId != null)
{
return Ok(customerId);
}
else
{
return NotFound();
}
}
catch(Exception ex)
{
return BadRequest();
}
}
return BadRequest();
}
[HttpPost]
[Route("DeleteCustomer")]
public async Task<IActionResult> DeleteCustomer(string customerId)
{
int result = 0;
if (customerId == null)
{
return BadRequest();
}
try
{
var customer = await _customersRepository.Delete(customerId);
if (customer == null)
{
return NotFound();
}
return Ok(customer);
}
catch
{
return BadRequest();
}
}
[HttpPost]
[Route("UpdateCustomer")]
public async Task<IActionResult> UpdateCustomer([FromBody] CustomerViewModel model)
{
if (ModelState.IsValid)
{
try
{
await _customersRepository.Update(model);
return Ok();
}
catch(Exception ex)
{
if (ex.GetType().FullName == "Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException")
{
return NotFound();
}
return BadRequest();
}
}
return BadRequest();
}
}
}
Swagger also cannot deal with two Classes having the same name (at least, not out of the box). So if you have two name spaces, and two classes having the same name, it will fail to initialize.
If you are on the broken Swashbuckle page, Open Dev Tools ... look at the 500 response that Swagger sends back and you will get some great insight.
Here's the dumb thing I was doing ... had a route in the HTTPGet as well as a ROUTE route.
[HttpGet("id")]
[ProducesResponseType(typeof(string), 200)]
[ProducesResponseType(500)]
[Route("{employeeID:int}")]
You are getting an error. Because of you doubled your action names. Look at this example.
Swagger – Failed To Load API Definition , Change [Route("GetCustomers")] names and try again.
I know this was is resolved, but I had this same problem today.
In my case, the problem was that I had a base controller class I created to other controllers inherit from.
The problem started to happen when I created a public function on the base class. Turning it to protected did the trick
This is usually indicative of controllers/actions that Swashbuckle doesn't support for one reason or another.
It's expected that you don't have a swagger.json file in your project. Swashbuckle creates and serves that dynamically using ASP.NET Core's ApiExplorer APIs. What's probably happening here is that Swashbuckle is unable to generate Swagger.json and, therefore, the UI is failing to display.
It's hard to know exactly what caused the failure, so the best way to debug is probably just to remove half your controllers (just move the files to a temporary location) and check whether the issues persists. Then you'll know which half of your controllers contains the troublesome action. You can 'binary search' removing controllers (and then actions) until you figure out which action method is causing Swashbuckle to not be able to generate Swagger.json. Once you know that, it should be obvious whether this is some issue in your code or an issue that should be filed in the Swashbuckle repo.
You could press F12 to open the chrome browser's developer tools to check the cause of failure ,then enter the failed request path and click on the error file to preview the detailed error .
It could also be an issue with ambiguous routes or something like that tripping Swashbuckle up. Once you've narrowed down the cause of failure to something more specific like that, it can either be fixed or filed, as appropriate.
If you want to access swagger via host:port/swagger/v1/swagger.json then you should add options: SwaggerGenOptions inside
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
c.SwaggerDoc("swagger/v1", new OpenApiInfo { Version = "1.0", Title = "API" });
);
}
It should work properly.
Hello i am trying to issue a http get request to a .NET Core Console App from my Angular 2 frontend and i get the following error:
Access to XMLHttpRequest at 'http://127.0.0.1:9300/api/getusers' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
For me it is curious since i have enabled CORS on the server side as you can see below in the Startup class.
Startup
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
services.AddMvc();
}
public IConfiguration Configuration;
public void Configure(IApplicationBuilder app) {
Console.WriteLine("request delievered");
Debug.WriteLine("Entered Server !");
app.UseMvc();
app.UseCors(x => { x.AllowAnyHeader(); x.AllowAnyOrigin();x.AllowAnyMethod(); });
}
}
I make the request from the UI like this:
#Injectable()
export class UserService{
private static baseUrl:string="http://127.0.0.1:9300/api";
constructor(private http:HttpClient) {
}
getClientsAsync():Promise<User[]>{
let route=UserService.baseUrl+"/getusers";
var data=(this.http.get(route) //should i have some headers here?
.map(resp=>resp)
.catch(err=>
Observable.throwError(err)
) as Observable<User[]>).toPromise<User[]>();
return data;
}
}
P.S I have tried with Postman and the request works ,however here in the angular 2 i have not included any headers for my http.get method.Could this be the problem ?
You need to put UseCors before UseMvc.
public void Configure(IApplicationBuilder app) {
Console.WriteLine("request delievered");
Debug.WriteLine("Entered Server !");
app.UseCors(x => { x.AllowAnyHeader(); x.AllowAnyOrigin();x.AllowAnyMethod(); });
app.UseMvc();
}
This is because UseCors adds a middleware (as does UseMvc), and middleware are executed in order from top to bottom. So the request never gets to the CORS middleware.
Is there a way I can get the response from a method without using a controller. I mean, in order to get the tenants from the database I use attribute binding and I get it from: "http://localhost:5000/api/tenants". Is there a way I can retrieve values without using a controller, like a service? For example in angular I use httpclient to get the response. Is there anything similar in .netcore 2 webapi? Thank, you!
For Controller, it uses UseMvc middleware to route the request to controller.
If you would not use controller, you could try custom middleware to return the data directly based on the request path.
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)
{
//your config
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//your config
app.Map("/tenants", map => {
map.Run(async context => {
var dbContext = context.RequestServices.GetRequiredService<MVCProContext>();
var tenants = await dbContext.Users.ToListAsync();
await context.Response.WriteAsync(JsonConvert.SerializeObject(tenants));
});
});
app.Run(async context => {
await context.Response.WriteAsync($"Default response");
});
}
}
I am using NSwag with an ASP.Net Core API, when I execute the web API and navigates to the Swagger UI it displays the following error:
Fetching resource list: undefined. Please wait. It gives an 404 and tells me that Cannot read property 'substring' of undefined, that when I tried to trace the error is pointing to the Swagger client in self.url.substring. Although the json displayed in the swagger.json is totally correct.
This is my Startup.cs class with the Explorer Solution at the right showing my nuget dependencies:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// 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();
}
app.UseStaticFiles();
// Enable the Swagger UI middleware and the Swagger generator
app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, settings =>
{
settings.SwaggerUiRoute = "/swagger";
settings.PostProcess = document =>
{
document.Info.Version = "v1";
document.Info.Title = "Analisis API";
document.Info.Description = "A simple ASP.NET Core web API";
document.Info.TermsOfService = "None";
document.Info.Contact = new NSwag.SwaggerContact
{
Name = "Example",
Email = "example#gmail.com",
Url = "http://google.es"
};
document.Info.License = new NSwag.SwaggerLicense
{
Name = "Use under LICX",
Url = "https://example.com/license"
};
};
app.UseMvc();
});
}
And this is my controller:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : Controller
{
public IDatosAnalisis datosManager = new DatosAnalisis();
public IResultado resultadoManager = new Resultado();
[HttpGet]
public ActionResult<String> GetDefault()
{
return "Bienvenido a AnalisisApi";
}
[HttpGet("getResultado/{patologiaId}")]
[ProducesResponseType(typeof(ResultadoDTO), 200)]
public ActionResult<ResultadoDTO> GetResultadoByPatologiaId(int patologiaId)
{
ResultadoDTO result = resultadoManager.getResultadoByPatologia(patologiaId);
return result;
}
/// <summary>
/// Receives the analisis data and evaluates them.
/// </summary>
[HttpPost]
public ActionResult<List<ShortResultDTO>> TestValoresAnalisis(DatosSujetoDTO datosSujeto)
{
List<ShortResultDTO> results = datosManager.postDatosAnalisisAndGetPatologias(datosSujeto);
return results;
}
}
Thanks in advance for any help given!
Same problem here, my workaround: using a custom url to visit Swagger
https://localhost:44336/swagger/index.html?url=/swagger/v1/swagger.json
I have an ASP.Net Core MVC web application which uses Azure AD for authentication. I have just received a new requirement to force user to reauthenticate before entering some sensitive information (the button to enter this new information calls a controller action that initialises a new view model and returns a partial view into a bootstrap modal).
I have followed this article which provides a great guide for achieving this very requirement. I had to make some tweaks to get it to work with ASP.Net Core 2.0 which I think is right however my problems are as follows...
Adding the resource filter decoration "[RequireReauthentication(0)]" to my controller action works however passing the value 0 means the code never reaches the await.next() command inside the filter. If i change the parameter value to say 30 it works but seems very arbitrary. What should this value be?
The reauthentication works when calling a controller action that returns a full view. However when I call the action from an ajax request which returns a partial into a bootstrap modal it fails before loading the modal with
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'https://localhost:44308' is therefore not allowed
access
This looks like a CORS issue but I don't know why it would work when going through the standard mvc process and not when being called from jquery. Adding
services.AddCors();
app.UseCors(builder =>
builder.WithOrigins("https://login.microsoftonline.com"));
to my startup file doesn't make any difference. What could be the issue here?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Ommitted for clarity...
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddCors();
// Ommitted for clarity...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Ommitted for clarity...
app.UseCors(builder => builder.WithOrigins("https://login.microsoftonline.com"));
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
AzureAdAuthenticationBuilderExtensions.cs
public static class AzureAdAuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
=> builder.AddAzureAd(_ => { });
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
builder.AddOpenIdConnect(options =>
{
options.ClaimActions.Remove("auth_time");
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = RedirectToIdentityProvider
};
});
return builder;
}
private static Task RedirectToIdentityProvider(RedirectContext context)
{
// Force reauthentication for sensitive data if required
if (context.ShouldReauthenticate())
{
context.ProtocolMessage.MaxAge = "0"; // <time since last authentication or 0>;
}
else
{
context.Properties.RedirectUri = new PathString("/Account/SignedIn");
}
return Task.FromResult(0);
}
internal static bool ShouldReauthenticate(this RedirectContext context)
{
context.Properties.Items.TryGetValue("reauthenticate", out string reauthenticate);
bool shouldReauthenticate = false;
if (reauthenticate != null && !bool.TryParse(reauthenticate, out shouldReauthenticate))
{
throw new InvalidOperationException($"'{reauthenticate}' is an invalid boolean value");
}
return shouldReauthenticate;
}
// Ommitted for clarity...
}
RequireReauthenticationAttribute.cs
public class RequireReauthenticationAttribute : Attribute, IAsyncResourceFilter
{
private int _timeElapsedSinceLast;
public RequireReauthenticationAttribute(int timeElapsedSinceLast)
{
_timeElapsedSinceLast = timeElapsedSinceLast;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var foundAuthTime = int.TryParse(context.HttpContext.User.FindFirst("auth_time")?.Value, out int authTime);
var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (foundAuthTime && ts - authTime < _timeElapsedSinceLast)
{
await next();
}
else
{
var state = new Dictionary<string, string> { { "reauthenticate", "true" } };
await AuthenticationHttpContextExtensions.ChallengeAsync(context.HttpContext, OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties(state));
}
}
}
CreateNote.cs
[HttpGet]
[RequireReauthentication(0)]
public IActionResult CreateNote(int id)
{
TempData["IsCreate"] = true;
ViewData["PostAction"] = "CreateNote";
ViewData["PostRouteId"] = id;
var model = new NoteViewModel
{
ClientId = id
};
return PartialView("_Note", model);
}
Razor View (snippet)
<a asp-controller="Client" asp-action="CreateNote" asp-route-id="#ViewData["ClientId"]" id="client-note-get" data-ajax="true" data-ajax-method="get" data-ajax-update="#client-note-modal-content" data-ajax-mode="replace" data-ajax-success="ShowModal('#client-note-modal', null, null);" data-ajax-failure="AjaxFailure(xhr, status, error, false);"></a>
All help appreciated. Thanks
The CORS problem is not in your app.
Your AJAX call is trying to follow the authentication redirect to Azure AD,
which will not work.
What you can do instead is in your RedirectToIdentityProvider function, check if the request is an AJAX request.
If it is, make it return a 401 status code, no redirect.
Then your client-side JS needs to detect the status code, and issue a redirect that triggers the authentication.