Get request outside a controller in .netcore - asp.net-core

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");
});
}
}

Related

How to return HTTP 404

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:

ASP.NET core Web API routing

Route using "UseMvc" but not able to call the controller
In startup page have added service.AddMvc method & in configure section it's app.useMvc()
I am not able to route and can't figure out what the problem is
The controller code is here and have route : the action method is Get with parameter start of DateTime type
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CurrencyContext>(cfg => {
cfg.UseSqlServer(_config.GetConnectionString("BitCoinIndexConnectionString"));
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseNodeModules(env);
app.UseMvc(routes =>
{
routes.MapRoute(name: "default",
template: "api/{controller}/{action}/{start:DateTime}",
defaults: new {
controller = "Currency",
action = "Get",
start = DateTime.Now.AddDays(-14)});
});
}
}
[Route("api/[Controller]")]
public class CurrencyController : Controller
{
private BitCoinRepository<BitCoinIndex> _repository;
public CurrencyController(BitCoinRepository<BitCoinIndex> repository)
{
_repository = repository;
}
// GET: api/<controller>
[HttpGet("{start}",Name ="Get")]
public IActionResult Get(DateTime start)
{
// var bci = _repository.GetByDates(start).ToDictionary(t => t.Date.ToString(), t => t.Rate);
return View();
}
}
I faced the same issue and resolved it using attribute routing. This is what I did. If you are not using .Net Core 3, ignore point 1.
1st disable endpoint routing by adding this in your ConfigureServices:
services.AddMvc(options => options.EnableEndpointRouting = false);
You can now use this in Configure method
app.UseMvc();
Next, just define your routes inside the controller (bear in mind I generally prefer routing by adding routes to the routing table, but encountered unnecassary issues going this 'route', attribute routing was the easiest 'route' to take).
[Route("api/myctrl")]
[ApiController]
public class MyControllerController : ControllerBase
{
[HttpGet]
[Route("getsomething")]
public async Task<JsonResult> DoStuff()
{
}
}
Access this by either using #Url.Action("DoStuff", "MyController"); or /api/myctrl/getsomething

Server uses Cors but UI still gets allow-origin error

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.

Set dummy IP address in integration test with Asp.Net Core TestServer

I have a C# Asp.Net Core (1.x) project, implementing a web REST API, and its related integration test project, where before any test there's a setup similar to:
// ...
IWebHostBuilder webHostBuilder = GetWebHostBuilderSimilarToRealOne()
.UseStartup<MyTestStartup>();
TestServer server = new TestServer(webHostBuilder);
server.BaseAddress = new Uri("http://localhost:5000");
HttpClient client = server.CreateClient();
// ...
During tests, the client is used to send HTTP requests to web API (the system under test) and retrieve responses.
Within actual system under test there's some component extracting sender IP address from each request, as in:
HttpContext httpContext = ReceiveHttpContextDuringAuthentication();
// edge cases omitted for brevity
string remoteIpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString()
Now during integration tests this bit of code fails to find an IP address, as RemoteIpAddress is always null.
Is there a way to set that to some known value from within test code? I searched here on SO but could not find anything similar. TA
You can write middleware to set custom IP Address since this property is writable:
public class FakeRemoteIpAddressMiddleware
{
private readonly RequestDelegate next;
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.168.1.32");
public FakeRemoteIpAddressMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Connection.RemoteIpAddress = fakeIpAddress;
await this.next(httpContext);
}
}
Then you can create StartupStub class like this:
public class StartupStub : Startup
{
public StartupStub(IConfiguration configuration) : base(configuration)
{
}
public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
base.Configure(app, env);
}
}
And use it to create a TestServer:
new TestServer(new WebHostBuilder().UseStartup<StartupStub>());
As per this answer in ASP.NET Core, is there any way to set up middleware from Program.cs?
It's also possible to configure the middleware from ConfigureServices, which allows you to create a custom WebApplicationFactory without the need for a StartupStub class:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost
.CreateDefaultBuilder<Startup>(new string[0])
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
public class CustomStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
next(app);
};
}
}
Using WebHost.CreateDefaultBuilder can mess up with your app configuration.
And there's no need to change Product code just to accommodate for testing, unless absolutely necessary.
The simplest way to add your own middleware, without overriding Startup class methods, is to add the middleware through a IStartupFilterā€ as suggested by Elliott's answer.
But instead of using WebHost.CreateDefaultBuilder, just use
base.CreateWebHostBuilder().ConfigureServices...
public class CustomWAF : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return base.CreateWebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
I used Elliott's answer within an ASP.NET Core 2.2 project. However, updating to ASP.NET 5.0, I had to replace the override of CreateWebHostBuilder with the below override of CreateHostBuilder:
protected override IHostBuilder CreateHostBuilder()
{
return Host
.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}

Conditionally use custom middleware

I created my custom authentication middleware in asp. net core project, and registered it as shown below:
public class MyAuthenticationMidleware
{
private readonly RequestDelegate _next;
public ConnectAuthenticationMidleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!UserIsAuthenticated())
{
context.Response.StatusCode = 401;
return;
}
...
await _next.Invoke(context);
}
}
public static class MyAuthenticationMidlewareExtensions
{
public static IApplicationBuilder UseMyAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyAuthenticationMidleware>();
}
}
In Startup:
public void Configure(...)
{
app.UseStaticFiles();
app.UseMyAuthentication();
app.UseMvc();
}
This works correctly - authentication middleware is run for each request. If user is not authenticated, 401 is returned. Otherwise specific mvc action is invoked.
What I tried to do was to prevent the authentication middleware from running for some specific actions. I used MapWhen method to create another extension method and used it as follows:
public static class MyAuthenticationMidlewareExtensions
{
public static IApplicationBuilder UseMyAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyAuthenticationMidleware>();
}
public static IApplicationBuilder UseMyAuthenticationWhen(this IApplicationBuilder builder, Func<HttpContext, bool> predicate)
{
return builder.MapWhen(predicate, applicationBuilder => applicationBuilder.UseMyAuthentication());
}
}
public void Configure(...)
{
app.UseStaticFiles();
app.UseMyAuthenticationWhen(context => context.Request.Path != "xyz");
app.UseMvc();
}
Unfortunately, this doesn't work as expected. The middleware is invoked only when path is different than "xyz", but it seems that it short-circuts the whole chain - no mvc specific actions or filters are invoked.
Probably my understanding of MapWhen is incorrect. Is there any way to get the result I want?
MapWhen creates a new pipeline branch when the supplied predicate is true, and that branch does not rejoin with the main branch where you have UseMvc().
You can change your extension method to use UseWhen instead of MapWhen. UseWhen rejoins with the main pipeline so that your UseMvc() will still get called.
Note: While the above link references aspnet-contrib, the UseWhen extension method is now part of Microsoft.AspNetCore.Http.Abstractions.
This allows you to keep UseMvc() explicit in your Configure method instead of hidden away in your authentication extension method, where it really has no business being.
MapWhen is used to seperate middleware pipeline. If you want to use mvc for branced pipeline you need to add separetely. So you should use .UseMvc(); in extension method like below:
public static IApplicationBuilder UseMyAuthenticationWhen(this IApplicationBuilder builder, Func<HttpContext, bool> predicate)
{
return builder.MapWhen(predicate, applicationBuilder =>
{
applicationBuilder.UseMyAuthentication();
applicationBuilder.UseMvc();
});
}
However i wouldn't go with your way. For authentication middleware i would implement my own middleware like Simple token based authentication/authorization in asp.net core for Mongodb datastore and use Authorize attribute for authorization mvc actions.