I would like to have second controller in my asp.net WebApi, but when i add it it not works... First Controller works OK
i have 404 not found in my browser
not any errors while run
whats wrong?
namespace testing.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering",
"Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
and the second is below
{
[Route("[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public int Get()
{
return 100050;
}
}
}
Can some one tell me whats wrong?
You're using the attribute [Route("[controller]")] on your controller class. The string [controller] means "the name of the class, without the actual WORD "Controller".
This means, the name of the controller is "Values" (or "WeatherForecast" for the previous controller).
So, the final url route you want is /Values, not /ValuesController.
You can read more about how this works on the MS Docs page (the whole page has a lot of good information, not just that section).
Related
I added the [Authorization] attribute to the below action to protect it from unauthorized access. I'm wondering if it's possible to return a custom error/exception for this?
In postman, it only shows the 401 status but the response of the body is empty.
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[Authorize]
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
If you are using.NET 5 and above you can implement the IAuthorizationMiddlewareResultHandler and return whatever http response code and data you want on the failed authorization. You can even return different response codes and data based on different authorization policy failures that you can define yourself
public class CustomAuthorizationMiddlewareResultHandler :
IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();
public async Task HandleAsync(
RequestDelegate next,
HttpContext context,
AuthorizationPolicy policy,
PolicyAuthorizationResult authorizeResult)
{
if (policyAuthorizationResult.Forbidden &&
policyAuthorizationResult.AuthorizationFailure.FailedRequirements.OfType<Show404Requirement>().Any();)
{
var bytes = Encoding.UTF8.GetBytes("Not found bro");
await HttpContext.Response.Body.WriteAsync(bytes, 0, bytes.Length);
context.Response.StatusCode = StatusCodes.Status404NotFound;
return;
}
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy,
policyAuthorizationResult);
}
}
More details on the topic here https://learn.microsoft.com/en-us/aspnet/core/security/authorization/customizingauthorizationmiddlewareresponse?view=aspnetcore-5.0
I want to try to filter data based on API key authentication using NET CORE where the key is stored in the header. each key has its own data. is there a reference that can help me with that?
for an example like this
sorry if my question is difficult to understand. thank you very much good luck always
You can use Request.Headers["ApiKey"] to get value of "ApiKey" header,
using that value do your filter logic
Below is work demo, you can refer to it.
Using the Custom Attributes, name the Attribute as ApiKeyAttribute.We will be using this attribute to decorate the controller so that any request that is routed to the attributed controller will be redirected to ApiKeyAttribute.
1.ApiKeyAttribute.cs :
public class ApiKeyAttribute : Attribute, IAsyncActionFilter
{
private const string APIKEYNAME = "ApiKey";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.HttpContext.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key was not provided"
};
return;
}
var appSettings = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
var apiKey = appSettings.GetValue<string>(APIKEYNAME);
if (!apiKey.Equals(extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key is not valid"
};
return;
}
await next();
}
}
2.Add the API Key inside the appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ApiKey": "aaaaaaaaaaaaa"
}
Add [ApiKey] to WeatherForecastController
[ApiKey]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
Result:
I created a new project from the ASP.NET Core Web API template in Visual Studio, and attempted to add a new controller, but I get a 404 response with this message when trying to GET from it:
Cannot GET /navigation
My program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
The working controller that came with the template:
using Microsoft.AspNetCore.Mvc;
namespace Test.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
My new controller:
using Microsoft.AspNetCore.Mvc;
using Test.API.Navigation;
namespace Test.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class NavigationController : ControllerBase
{
public NavigationController()
{
}
[HttpGet(Name = "GetNavigation")]
public IEnumerable<NavigationItem> Get()
{
return new List<NavigationItem>
{
new NavigationItem()
{
Title = "Home",
Url = "test",
AltText = "Home page"
}
};
}
}
}
How can I get this controller to work? The project seems to have no extra config files that are relevant, just launchsettings.json and applicationsettings.json.
I tried changing the name of the WeatherForecastController, which led to it returning the same response, so it feels like it is hardcoded somehow.
Did you update your proxy.conf.js file? Change the context to /navigation. The target is your target you have now.
const PROXY_CONFIG = [
{
context: [
"/navigation",
],
target: "https://localhost:7105",
secure: false
}
]
module.exports = PROXY_CONFIG;
Program.cs
var builder = WebApplication.CreateBuilder(args);
// Use Autofac
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// 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();
// Replace the default controller activator
//builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, CustomControllerActivator>());
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
CustomControllerActivator.cs
public class CustomControllerActivator : IControllerActivator
{
public object Create(ControllerContext context)
{
// `serviceProvider` the object is `Autofac.Extensions.DependencyInjection.AutofacServiceProvider` type
var serviceProvider = context.HttpContext.RequestServices;
...
}
public void Release(ControllerContext context, object controller)
{
...
}
}
WeatherForecastController.cs
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
private readonly IServiceScope serviceProvider;
public WeatherForecastController(ILogger<WeatherForecastController> logger,IServiceProvider serviceProvider)
{
_logger = logger;
//`serviceProvider` the object is `Autofac.Extensions.DependencyInjection.AutofacServiceProvider` type
serviceProvider = serviceProvider;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
I checked the source code and didn't see you replace 'IControllerActivator' or 'IControllerActivatorProvider', so I don't quite understand how you operate the activation of the controller
I don't understand how you replace the object of 'HttpContext.RequestServices' with the type of 'Autofac.Extensions.DependencyInjection.AutofacServiceProvider'
Thank you very much for asking for relief
In .NET 6 it is possible to create minimal APIs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products/{id}", (int id) => { return Results.Ok(); })
app.MapGet("/users/{id}", (int id) => { return Results.Ok(); })
app.Run();
What would be an approach to group endpoints in multiple files instead of having all in Program file?
ProductEndpoints.cs:
app.MapGet("/products/{id}", (int id) => { return Results.Ok(); })
UserEndpoints.cs
app.MapGet("/users/{id}", (int id) => { return Results.Ok(); })
Only one file with top-level statement is allowed per project. But nobody forbids moving endpoints to some static method of another class:
public static class ProductEndpointsExt
{
public static void MapProductEndpoints(this WebApplication app)
{
app.MapGet("/products/{id}", (int id) => { return Results.Ok(); });
}
}
And in the Program file:
app.MapProductEndpoints();
We can use partial Program.cs files too
Example: "Program.Users.cs"
partial class Program
{
/// <summary>
/// Map all users routes
/// </summary>
/// <param name="app"></param>
private static void AddUsers(WebApplication app)
{
app.MapGet("/users", () => "All users");
app.MapGet("/user/{id?}", (int? id) => $"A users {id}");
///post, patch, delete...
}
}
And in "Program.cs"
...
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//add...
AddUsers(app);
...
What I did is creating a interface IEndPoint that each class that need to define endpoints must implement, and an extension method to find all implementations to call the interface mapping method.
You just have to call that extension method in your Program.cs or Startup to register all the endpoints.
// IEndpoint.cs
public interface IEndPoint
{
void MapEndpoint(WebApplication app);
}
// FeatureA.cs
public class FeatureA: IEndPoint
{
public void MapEndpoint(WebApplication app)
{
app.MapGet("api/FeatureA/{id}", async (int id) => $"Fetching {id} data");
}
}
// WebApplicationExtension.cs
public static class WebApplicationExtensions
{
public static void MapEndpoint(this WebApplication app)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var classes = assemblies.Distinct().SelectMany(x => x.GetTypes())
.Where(x => typeof(IEndPoint).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract);
foreach (var classe in classes)
{
var instance = Activator.CreateInstance(classe) as IEndPoint;
instance?.MapEndpoint(app);
}
}
}
// Program.cs
...
app.MapEndpoint();
...
I think the best way is to use Controller based web service. Although, you can this approach like this:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapWeatherForecastRoutes();
app.Run();
internal static class WeatherForecastController
{
internal static void MapWeatherForecastRoutes(this WebApplication app)
{
app.MapGet("/weatherforecast", () =>
{
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
}
}
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
The only thing we need to consider is how to make the best use of extension methods.
It is enough to implement each group of web services in a static class and add them to the program using Extension methods.
Another option is to use Carter project
Add carter project to Nuget dotnet add package carter
Modify Program.cs to use carter
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCarter();
var app = builder.Build();
app.MapCarter();
app.Run();
Notice that .AddControllers() can be removed
Add Carter Module, it will be later auto-discovered
using Carter;
using MapEndpoints;
public class WeatherModule : ICarterModule
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapGet("/GetWeatherForecast", (ILoggerFactory loggerFactory) => Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray());
}
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}