LinkGenerator.GetUriByAction not following the custom route - api

That's the controller:
[ApiController]
[Route("api/[controller]")
public class MarketController : ControllerBase
{
[HttpGet("{id}/picture")
public async Task<IActionResult> GetPictureAsync(Guid id)
{
...
}
}
I'm using LinkGenerator to create a Absolute URI from GetPictureAsync. And set the Startup class to start HttpContextAccessor as DI.
// Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();
});
}
And in my custom class I use that way:
public class CustomClass
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly LinkGenerator _linkGenerator;
public CustomClass(IHttpContextAccessor httpContextAccessor,
LinkGenerator linkGenerator)
{
_httpContextAccessor = httpContextAccessor;
_linkGenerator = linkGenerator;
}
public void SomeMethod()
{
var uri = _linkGenerator.GetUriByAction(_httpContextAccessor.HttpContext, "GetPicture", "Markets", new { id = id });
}
}
The problem is because LinkGenerator is not following the custom route sample that I set in GetPicture method.
The LinkGenerator generates the following value:
https://localhost:5051/Markets/GetPicture/00748d23-afa7-4efb-b67b-77f68fdc44d5
But it should generate:
https://localhost:5051/api/Markets/00748d23-afa7-4efb-b67b-77f68fdc44d5/picture

The reason is you use wrong controller name in SomeMethod. Follow the steps you provided, I reproduced your issue.
You should use Market, not Markets.
Because your controller name is MarketController.
After test it,it works for me.

Related

Unable to cast object of type 'MyAPI.Api.Middleware.MyFilter' to type 'Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata'

My requirement is to modify the response from the controller before sending it back to the client.
I am trying to implement IActionResult interface.
public class MyFilter: IActionResult
{
ApiResponse _response = new ApiResponse();
public async Task ExecuteResultAsync(ActionContext context)
{
var httpResponse = context.HttpContext.Response;
if (httpResponse != null)
{
if (httpResponse.StatusCode == 200)
{
if (context.Result.GetType().Equals(typeof(Microsoft.AspNetCore.Mvc.ObjectResult)))
{
_response.Response = ((Microsoft.AspNetCore.Mvc.ObjectResult)context.Result).Value;
_response.Errors = null;
_response.IsSuccess = true;
}
}
}
return ;
}
}
my program.cs has
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSingleton<MyFilter>();
builder.Services.AddAppServices();
builder.Services.AddControllers();
builder.Services.AddDataContext(builder.Configuration);
builder.Services.AddRepositories();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseRouting();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
my controller has the attribute
[Route("api/[controller]")]
[ApiController]
[ServiceFilter(typeof(MyFilter))]
public class MyController : ControllerBase
{
}
But when I invoke any of the controller action methods it doesn't reach and gives me an exception as response.
is there something I am missing completely?
You should implement IActionFilter or IAsynActionFilter not IActionResult!
To create an Acton filter, we need to create a class that inherits either from the IActionFilter interface or IAsyncActionFilter interface or from the ActionFilterAttribute class which is the implementation of the IActionFilter, IAsyncActionFilter, and a few different interfaces as well:
public abstract class ActionFilterAttribute : Attribute, IActionFilter, IFilterMetadata,
IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter
for example:
namespace ActionFilters.Filters
{
public class ActionFilterExample : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// our code before action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// our code after action executes
}
}
}
then on your startup.cs (or program.cs in .net 6 and above):
builder.Services.AddScoped<ActionFilterExample>();
Finally, to use a filter registered on the Action or Controller level, we need to place it on top of the Controller or Action as a ServiceType:
namespace AspNetCore.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[ServiceFilter(typeof(ActionFilterExample))]
public IEnumerable<string> Get()
{
return new string[] { "example", "data" };
}
}
}
read more: codeMaze microsoft docs

ASP.NetCore API controller does not call my method in my Controller

I create an API Controller in my ASP.NET core web api project.
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
public MyData Method1(string Id)
{
Console.WriteLine("here");
return null;
}
public MyData Method2()
{
Console.WriteLine("here");
return null;
}
}
I set breakpoints in each method and load these urls in my browser:
https://localhost:44357/test/Method1/1343a
https://localhost:44357/test/Method2
And in my Startup.cs, I have
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Can you please help me why my API controller does not get invoked when I load ^ urls in browser?
I read https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0 , I think those method should get called.
Thank you for your help.
First check by adding verb over method [HTTPPOST] or [HTTPGET].
Also your starup.cs code looks like as written below:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
You controller Code:
[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
[HttpGet]
[Route("method1")]
public MyData Method1(string Id)
{
Console.WriteLine("here");
return null;
}
[HttpGet]
[Route("method2")]
public MyData Method2()
{
Console.WriteLine("here");
return null;
}
}
Now call you API like :
https://localhost:portno/api/test/method1/abc
https://localhost:portno/api/test/method2
Please change your route to include the action like below
[ApiController]
[Route("[controller]/[action]")]
public class TestController : ControllerBase
{
public MyData Method1(string Id)
{
Console.WriteLine("here");
return null;
}
public MyData Method2()
{
Console.WriteLine("here");
return null;
}
}
in Asp.net Api you need route for each Action:
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
[HttpGet("[Action]/{Id}")]
public MyData Method1(string Id)
{
Console.WriteLine("here");
return null;
}
[HttpGet("[Action]")]
public MyData Method2()
{
Console.WriteLine("here");
return null;
}
}
For Net Core 6.0 change this in your Program.cs
`var services = builder.Services;
services.AddControllers();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();`
and [Route("methodname")] use this attribute above your action method.

How to access session in class library .Net Core 5

is there a way to access HttpContext.session in a class library? (im using .Net Core 5)
I have the configuration all set up and also using microsoft.AspNetCore.Http but im still unable to access the session variables.
If there is no way to do that whats the best way to perform actions that require the current User's ID/Identification?
As #King King answered, you could inject the IHttpContextAccessor into the class.
Step 1 Add Session service
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSession();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
...
}
Step 2 Access Session in custom class
public class SessionTest
{
private readonly IHttpContextAccessor _httpContextAccessor;
private ISession _session => _httpContextAccessor.HttpContext.Session;
public SessionTest(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void setSession()
{
_session.SetString("Test", "Hello World!");
}
public void getSession()
{
var message = _session.GetString("Test");
...
}
}
Step 3 access session via custom class
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISession _session;
public HomeController(IHttpContextAccessor httpContextAccessor,ILogger<HomeController> logger)
{
_logger = logger;
_httpContextAccessor = httpContextAccessor;
_session = _httpContextAccessor.HttpContext.Session;
}
public IActionResult Index()
{
SessionTest session = new SessionTest(_httpContextAccessor);
session.setSession();
session.getSession();
return View();
}
}

inject Database Context into Custom Attribute .NET Core

I'm creating ASP.NET Core 3.1 app, using SPA for front end. So I decided to create custom Authentication & Authorization. So I created custom attributes to give out and verify JWTs.
Lets say it looks like this:
[AttributeUsage(AttributeTargets.Method)]
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
public async void OnAuthorization(AuthorizationFilterContext filterContext)
{
//Checking Headers..
using (var EF = new DatabaseContext)
{
user = EF.User.Where(p => (p.Email == username)).FirstOrDefault();
}
filterContext.HttpContext.Response.Headers.Add(
"AccessToken",
AccessToken.CreateAccessToken(user));
}
}
Everything was Okay, but my DatabaseContext, looked like this:
public class DatabaseContext : DbContext
{
public DbSet<User> User { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("ConnectionString");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//....
}
}
I wanted to take Connection string from Appsettings.json and maybe use Dependency injection. I
Changed Startup.cs to look like this:
//...
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<DatabaseContext>(
options => options.UseMySQL(Configuration["ConnectionStrings:ConnectionString"]));
services.Add(new ServiceDescriptor(
typeof(HMACSHA256_Algo), new HMACSHA256_Algo(Configuration)));
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
//...
Changed Database Context class to this:
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<User> User { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
///..
}
}
In Controllers I injected DB context and everything works. It looks like this:
[ApiController]
[Route("API")]
public class APIController : ControllerBase
{
private DatabaseContext EF;
public WeatherForecastController(DatabaseContext ef)
{
EF = ef;
}
[HttpGet]
[Route("/API/GetSomething")]
public async Task<IEnumerable<Something>> GetSomething()
{
using(EF){
//.. this works
}
}
}
But my custom Attribute doesn't work no more. I can't declare new Database context, because it needs DatabaseContextOptions<DatabaseContext> object to declare, so how do I inject DBContext to Attribute as I did to Controller?
This doesn't work:
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
private DatabaseContext EF;
public AuthLoginAttribute(DatabaseContext ef)
{
EF = ef;
}
public async void OnAuthorization(AuthorizationFilterContext filterContext)
{
using(EF){
}
}
}
this works with controller, but with attribute complains about there not being constructor with 0 arguments.
What you can do is utilize the RequestServices:
[AttributeUsage(AttributeTargets.Method)]
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var dbContext = context.HttpContext
.RequestServices
.GetService(typeof(DatabaseContext)) as DatabaseContext;
// your code
}
}
If you allow me to add two comments to your code:
Try not to use async void because in the event of an exception you will be very confused what is going on.
There is no need to wrap injected DbContext in a using statement like this using(EF) { .. }. You will dispose it early and this will lead to bugs later in the request. The DI container is managing the lifetime for you, trust it.

Access to the generalized collection of hubs

I am tormented by the question, if I add several hubs (hub1, hub2, ...) to the project (asp.core), can I get somewhere a generalized collection of these hubs, or their contexts? Something like:
public class SomeClass
{
private readoly IHubCollection _collection;
public SomeClass(IHubCollection collection)
=> _collection = collection;
public void SomeMethod()
{
foreach(vat hub in _collection)
{
hub.SendSomeMessage();
}
}
}
For your requirement, there are some limitions like the hub need to implement the same interfance which contains SendSomeMessage.
Try following steps below:
IHub
public interface IHub
{
void SendSomeMessage();
}
ChatHub
public class ChatHub : Hub, IHub
{
public void Send(string name, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.SendAsync("broadcastMessage", name, message);
}
public void SendSomeMessage()
{
Clients.All.SendAsync("broadcastMessage", "hub", "hello");
}
}
Register Hub
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)
{
services.AddSignalR();
services.AddMvc();
services.AddSingleton<ChatHub>();
}
// 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.UseFileServer();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chat");
});
app.UseMvcWithDefaultRoute();
}
}
UseCase
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly IServiceProvider _serviceProvider;
public ValuesController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
// GET: api/<controller>
[HttpGet]
public async Task<IEnumerable<string>> Get()
{
var typesFromAssemblies = Assembly.GetExecutingAssembly().GetTypes().Where(x => x.BaseType == typeof(Hub));
foreach (var type in typesFromAssemblies)
{
var hub = _serviceProvider.GetService(type) as IHub;
hub.SendSomeMessage();
}
return new string[] { "value1", "value2" };
}
}