HttpGetAttribute doesn't work in core web api - rest

Well known situation. I need two endpoints
GetAll -> api/brands
GetById -> api/brands/1
[ApiController]
[Route("api/[controller]")]
public class BrandsController : ControllerBase
{
private readonly BrandRepository repository;
public BrandsController(BrandRepository repository)
{
this.repository = repository;
}
[HttpGet("{id:int}")]
public async Task<ActionResult> GetById(int id)
{
var brand = await repository.FindAsync(id);
if (brand == null)
{
return NotFound();
}
return Ok(brand);
}
[HttpGet("")]
public ActionResult<IEnumerable<Brand>> GetAll()
{
var brands = repository.GetAll().ToList();
return Ok(brands);
}}
So, I always get into GetAll()
Any ideas? Help, please :)
Is it a correct namespace?
using Microsoft.AspNetCore.Mvc;
for
[HttpGet]
Startup.cs
namespace BackOffice
{
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.AddControllers();
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseMySql(Configuration.GetConnectionString("local")));
services.AddTransient<BrandRepository, BrandRepository>();
}
// 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.UseRouting();
app.UseAuthorization();
app.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
});
app.UseCors();
}
}
}
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd

Change your the attribute on your GetAll action to simply [HttpGet] and then change the attribute on your GetById action to [HttpGet("{id}")] .
You can use a constraint to id if need be but in your case I don't see any need for it. Generally you can use constraints when you have multiple actions on the same route but with different parameter types. For example, "api/brands/1" to get by integer ID and then maybe you have another action that is mapped to "api/brands/gucci" that will search for the brand by string name. Then you can use the {id:int} and {id:string} constraints in your route template to define which action to invoke.
Also make sure you use IActionResult when declaring the action return types. You don't want to use the concrete ActionResult type. Code samples below.
For the GetById action :
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var brand = await repository.FindAsync(id);
if (brand == null)
{
return NotFound();
}
return Ok(brand);
}
For your GetAll action :
[HttpGet]
public IActionResult<IEnumerable<Brand>> GetAll()
{
var brands = repository.GetAll().ToList();
return Ok(brands);
}
This will tell the routing middleware which action to invoke. For actions that you want mapped to the base controller route (i.e. "api/brands"), just use the attribute without an overload. Such as [HttpGet], [HttpPost], [HttpDelete]. For the actions that have a route parameter then you can use [HttpGet("{id}")] and so forth depending on the HTTP method. Don't worry about defining the type of the parameter in the attribute route template. You define the parameter in your action's parameters. For instance:
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
// Code here
return Ok();
}
If you want to map a route to something like "api/brands/designers/2" then you would use a template like [HttpGet("designers/{id}")] to do so. Don't put a "/" before the designers.
Edit : Forgot to mention, make sure your Startup.cs is properly configured for Web API routing. You can read the specifics on the ASP.NET Core 3.1 docs for what all the different options do. If you used the Web API template then it's probably fine but it's worth double checking as improperly configured endpoint routing can cause issues. Make sure you have the following in your Configure method in Startup.cs.
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Make sure that app.UseRouting(); is called before app.UseEndpoints();

Related

Is there any way to register tenant specific controller in asp.net core using autofac?

I am working on an application built on asp.net core 3.1 + Autofac as DI. Now i want to implement multi tenant feature. I am able to register tenant specific services and it is working as expected.
What i want to achieve is to register tenant specific controller to override default controller registered in main/application level container.
I have registered custom IApplicationFeatureProvider to register only main controllers.
here is the code.
//Startup.cs
public class StartUp
{
void ConfigureServices(IServiceCollection services)
{
services.AddControllers().ConfigureApplicationPartManager(apm =>
{
apm.FeatureProviders.RemoveAt(0); //remove default controller feature provider
apm.FeatureProviders.Add(new MyControllerFeatureProvider()); //register custom provider
}).AddControllersAsServices();
services.AddAutofacMultitenantRequestServices();
//rest of the configuration
}
public void ConfigureContainer(ContainerBuilder builder)
{
//registration of global or main services
builder.RegisterType<MyTenantIdentificationStrategy>()
.As<ITenantIdentificationStrategy>().SingleInstance(); //Get Tenant from request header
builder.Register(container =>
{
ITenantIdentificationStrategy strategy =
container.Resolve<ITenantIdentificationStrategy>();
// tenant resolution code
return new Tenant();
}).InstancePerLifetimeScope();
}
public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
var strategy <-- resolved from container
MultitenantContainer mtc = new MultitenantContainer(strategy, container);
mtc.ConfigureTenant(1, cb => {
cb.RegisterType<IP.Controllers.Extended.HomeController>).InstancePerLifetimeScope();
});
return mtc;
}
}
//HomeController.cs
namespace IP.Controllers
{
[Route("[controller]")]
public class HomeController : ControllerBase
{
}
[Route("Get")
public IActionResult Get()
{
return new JsonResult(new {Main = true});
}
}
//HomeController1.cs
namespace IP.Controllers.Extended <-- different namespace
{
[Route("[controller]")]
public class HomeController : ControllerBase
{
}
[Route("Get")
public IActionResult Get()
{
return new JsonResult(new {Main = false});
}
}
//program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
http://localhost:8082/Home/Get [Without tenantid in header]
http://localhost:8082/Home/Get [Without tenantid=1 in header]
In both case main home controller is resolved not tenant specific..
Any help well be appreciated.
Answer to my own question,
Routing and controller selection is handled Asp.Net Core not by Autofac.
I am registering tenant specific controllers in ConfigureTenant method. Asp.Net Core Framework is not aware about these controllers. So for given route framework always selects registered controller not extended one registered in ConfigureTenant method.
So first i have removed custom application feature provider. Now all controllers in current assembly are registered. Then removed separate registration of controllers from ConfigureTenant method. Now framework throws ambiguity exception because there are two controllers with same route.
From stack trace, i found that EndPointResolver implementation is throwing ambiguity exception. Since we can provide our own implementation for EndPointResolver, i create custom EndPointResolver and register it.
Now during route selection, if there is ambiguity while selecting controller, i just retrieve tenant information from request header and based on Tanent i am handling ambiguity exception.
I am not sure if it is the correct approach but now i am able to register controller with same name [same Route] in different namespace/assembly.

EFCore Include function works in Get endpoint but does not work in Put endpoint

Hi I am working with IdentityServer4 and EFCore 3.1.0 recently and have a very weird issue with the following two pieces of code. The result is that in ApiResourcesGetByIdController, apiResource in GetById function includes the value of scope and secret. However in ApiResourcesUpdateController, result in Update function does not include the value of scope and secret.
The usage of these two endpoints is that, the frontend calls the GetById endpoint to get ApiResource object and makes some modification in frontend. Then the frontend calls Update endpoint to update the object in database.
Can anyone help to see what could be wrong? Thank you
[Authorize]
[Route("ApiResources")]
[ApiController]
public class ApiResourcesGetByIdController : ControllerBase
{
private readonly ConfigurationDbContext _configurationDbContext;
public ApiResourcesGetByIdController(
ConfigurationDbContext configurationDbContext)
{
_configurationDbContext = configurationDbContext;
}
[HttpGet]
[Route("")]
public IActionResult GetById([FromQuery]int id)
{
ApiResource apiResource = _configurationDbContext.ApiResources
.Include(apiResource => apiResource.Scopes)
.Include(apiResource => apiResource.Secrets)
.FirstOrDefault(apiResource => apiResource.Id == id);
}
}
[Authorize]
[Route("ApiResources")]
[ApiController]
public class ApiResourcesUpdateController : ControllerBase
{
private readonly ConfigurationDbContext _configurationDbContext;
public ApiResourcesUpdateController(
ConfigurationDbContext configurationDbContext)
{
_configurationDbContext = configurationDbContext;
}
[HttpPut]
[Route("")]
public IActionResult Update([FromBody] ApiResource resource )
{
ApiResource result = _configurationDbContext.ApiResources
.Include(apiResource => apiResource.Scopes)
.Include(apiResource => apiResource.Secrets)
.FirstOrDefault(ar => ar.Id == resource.Id);
return Ok()
}
}
Not sure why but I deleted the file and recreated it. Then it works by itself.

Security Claims in DBCommandInterceptor

i am currently using EF and .NET Core 3 through Radzen to build an application. This is working fine, but I want to add additional logging to the database. In order to do so, I would like to make use of the DBCommandInterceptor as shown here to do some post query commands.
Is it possible to get the Claims of the Microsoft Authorization in this Interceptor class? In my normal controller class, I can simply call
var userId = User.FindFirst(ClaimTypes.Name).Value;
This doesn't work in the Interceptor and to be honest, my knowledge about that framework is very poor. I cannot even tell you why I can access the User reference in my ObjectController against the DBCommandInterceptor
If you add the Interceptor in DbContext.OnConfiguring, you can pass any state to it you want.
So require your DbContext to accept an Identity, or with a service dependency it can use to access the user. something like:
public class Db : DbContext
{
ClaimsIdentity user;
public Db(ClaimsIdentity user)
{
this.user = user;
}
Then configure the Interceptor to accept the User, or the DbContext instance. eg
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;Database=EFCore3Test;Integrated Security = true", a => a.UseRelationalNulls(true))
.ConfigureWarnings(c => c.Log((RelationalEventId.CommandExecuting, LogLevel.Information)))
.UseLoggerFactory(MyLoggerFactory)
.AddInterceptors(new MyCommandInterceptor(this));
base.OnConfiguring(optionsBuilder);
}
And have the interceptor use the constructor argument:
public class MyCommandInterceptor : DbCommandInterceptor
{
private Db db;
public MyCommandInterceptor(Db db)
{
this.db = db;
}
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
var userId = db.user.FindFirst(ClaimTypes.Name).Value;
//. . .
return base.ReaderExecuting(command, eventData, result);
}
}

Attribute routing with default root parameter in WebAPI

I have an API where all methods need a fixed parameter {customer} :
/cust/{customerId}/purchases
/cust/{customerId}/invoices
/cust/{customerId}/whatever*
How can I map all controllers to receive this parameter by default in a reusable way like:
endpoints.MapControllerRoute(name: "Default", pattern: "/cust/{customerId:int}/{controller}*"
I am using .net core 3.0 with the new .useEndpoints method on Startup.
You could create an implementation of ControllerModelConvention to custom the attribute route behavior. For more details, see official docs.
For example, suppose you want to combine an attribute route convention (like /cust/{customerId:int}/[controller]) with the existing attribute globally, simply create a Convention as below:
public class FixedCustomIdControllerConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
var customerRouteModel= new AttributeRouteModel(){
Template="/cust/{customerId:int}",
};
var isApiController= controller.ControllerType.CustomAttributes.Select(c => c.AttributeType)
.Any(a => a == typeof(ApiControllerAttribute));
foreach (var selector in controller.Selectors)
{
if(!isApiController)
{
var oldAttributeRouteModel=selector.AttributeRouteModel;
var newAttributeRouteModel= oldAttributeRouteModel;
if(oldAttributeRouteModel != null){
newAttributeRouteModel= AttributeRouteModel.CombineAttributeRouteModel(customerRouteModel, oldAttributeRouteModel);
}
selector.AttributeRouteModel=newAttributeRouteModel;
} else{
// ApiController won't honor the by-convention route
// so I just replace the template
var oldTemplate = selector.AttributeRouteModel.Template;
if(! oldTemplate.StartsWith("/") ){
selector.AttributeRouteModel.Template= customerRouteModel.Template + "/" + oldTemplate;
}
}
}
}
}
And then register it in Startup:
services.AddControllersWithViews(opts =>{
opts.Conventions.Add(new FixedCustomIdControllerConvention());
});
Demo
Suppose we have a ValuesController:
[Route("[controller]")]
public class ValuesController : Controller
{
[HttpGet]
public IActionResult Get(int customerId)
{
return Json(new {customerId});
}
[HttpPost("opq")]
public IActionResult Post(int customerId)
{
return Json(new {customerId});
}
[HttpPost("/rst")]
public IActionResult PostRst(int customerId)
{
return Json(new {customerId});
}
}
After registering the above FixedCustomIdControllerConvention, the routing behavior is:
The HTTP Request GET https://localhost:5001/cust/123/values will match the Get(int customerId) method.
The HTTP Request POST https://localhost:5001/cust/123/values/opq will match the Post(int customerId) method
Because we intentionally put a leading slash within /rst, the global convention is passed by. As a result, the POST https://localhost:5001/rst will match the PostRst(int customerId) method( with customId=0)
In case you're using a controller annotated with [ApiController]:
[ApiController]
[Route("[controller]")]
public class ApiValuesController : ControllerBase
{
[HttpGet]
public IActionResult Get([FromRoute]int customerId)
{
return new JsonResult(new {customerId});
}
[HttpPost("opq")]
public IActionResult Post([FromRoute]int customerId)
{
return new JsonResult(new {customerId});
}
[HttpPost("/apirst")]
public IActionResult PostRst([FromRoute]int customerId)
{
return new JsonResult(new {customerId});
}
}
You probably need decorate the parameter from routes with [FromRoute].

Web Api Get Overload Throws Multiple actions were found that match the request

I have an api controller:
[RoutePrefix("api/users")]
[Authorize]
public class UsersController : ApiController
that has two Get methods:
[HttpGet]
[Route("")]
public async Task<HttpResponseMessage> Get(ODataQueryOptions<ApplicationUser> options)
{
return Request.CreateResponse(HttpStatusCode.OK, new List<ApplicationUser>());
}
[HttpGet]
[Route("")]
public async Task<HttpResponseMessage> Get()
{
return Request.CreateResponse(HttpStatusCode.OK, new List<ApplicationUser>());
}
Calling http://mysite/api/users?$filter=FirstName eq 'George'
or
Calling http://mysite/api/users
causes the exception Multiple actions were found that match the request.
Commenting out either method will cause the other to work.
Any help would be appreciated.
All the Web API routing is about converting url into controller/action. And its mapping must be unambiguous.
In case that we would have only the first actionGet(ODataQueryOptions<ApplicationUser> options) it would match both urls below:
http://mysite/api/users?$filter=FirstName+eq+'George'
http://mysite/api/users
The first url will be converted into call Get(someODataValue), the second could be Get(null)
The same could be applied to second method Get() without params, because both urls will be converted into parameterless call Get() (OData part will be skipped)
So the solution usually should be in two methods, which are really unique by params. E.g. one is object/refence the second is valueType/int
[HttpGet]
public async Task<HttpResponseMessage> Get(ODataQueryOptions<ApplicationUser> options)
{ ... }
[HttpGet]
public async Task<HttpResponseMessage> Get(int id)
{ ... }