Api Routing in .net core 3.0 - asp.net-core-webapi

I am new to .net core and I want to set a MapRoute to my api.
I config endpoint in startup.cs like
app.UseEndPoints(endpoints=>{
endpoints.MapController("defaultApi","v1/{controller="cont"}/{action}/{id?}");
endpoints.MapController("defaultNonActionApi","v1/{controller="cont"}/{id?}")l
endpoints.MapHealthChecks("/healthz");
});
and in controller.cs
[ApiController]
[Route("[controller]")]
public class contController:ControllerBase{
[HttpGet]
public ActionResult Get(){}
}
The config in useEndpoints is useless, if I goto https://localhost:port/v1/cont it return a 404 and if i goto https://localhost:port/cont it can return an expected result.
It seems like the RouteAttribute override the config in useEndpoints but I can't remove RouteAttribute for an ApiController.
I know change Route("[controller]") to Route("v1/[controller]") can solve this problem, but what I want is something more global that can be configured in startup.cs.
Thanks.

Here is a simple demo like below:
1.Create a custom MvcOptionsExtensions:
public static class MvcOptionsExtensions
{
public static void UseGeneralRoutePrefix(this MvcOptions opts, IRouteTemplateProvider routeAttribute)
{
opts.Conventions.Add(new RoutePrefixConvention(routeAttribute));
}
public static void UseGeneralRoutePrefix(this MvcOptions opts, string prefix)
{
opts.UseGeneralRoutePrefix(new RouteAttribute(prefix));
}
}
public class RoutePrefixConvention : IApplicationModelConvention
{
private readonly AttributeRouteModel _routePrefix;
public RoutePrefixConvention(IRouteTemplateProvider route)
{
_routePrefix = new AttributeRouteModel(route);
}
public void Apply(ApplicationModel application)
{
foreach (var selector in application.Controllers.SelectMany(c => c.Selectors))
{
if (selector.AttributeRouteModel != null)
{
selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_routePrefix, selector.AttributeRouteModel);
}
else
{
selector.AttributeRouteModel = _routePrefix;
}
}
}
}
2.Register in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(o => { o.UseGeneralRoutePrefix("v1"); });
}
// 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();
});
}
3.Controller:
[ApiController]
[Route("[controller]")]
public class contController:ControllerBase{
[HttpGet]
public ActionResult Get(){}
}

Related

IHttpContextAccessor.HttpContext is null

I'm using DotNet 5.0, and even though I register HttpContextAccessor in the Startup.ConfigureServices whenever I try to reach it inside the code, IHttpContextAccessor.HttpContext is always null.
This is my Startup.cs file's ConfigureServices Method
public void ConfigureServices(IServiceCollection services)
{
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
this.Configuration = new ConfigurationBuilder().SetBasePath(System.IO.Directory.GetCurrentDirectory()).AddJsonFile($"appsettings.{environmentName}.json").Build();
services.AddControllers();
services.AddSimpleInjector(container, options =>
{
options.AddAspNetCore();
});
services.AddHttpContextAccessor();
}
And this is the Configure method of the same file:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSimpleInjector(container);
container.Verify();
}
And also I'm using hangfire, but whenever a job gets triggered I cannot reach HttpContextAccessor through dependency injection. It is always null.
For example this is one of my business layer files:
public class FooService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public FooService(IHttpContextAccessor httpContextAcessor)
{
_httpContextAccessor = httpContextAccessor;
}
public DoSomething()
{
var tryoutVar = _httpContextAccessor.HttpContext;
}
}
The tryoutVar variable is set to null, always.
Why is this happening?

No operations defined in spec! asp .net core 3.1, swagger is not showing controller

I tried this and also this several links but not getting the answer.
this is my startup.cs file
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddCommonService(Configuration);
services.AddSecurityServiceRepositories();
services.AddSwaggerService();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwaggerService();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseStaticFiles();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//app.UseMvc();
}
Adding service class, it adds the repositories
namespace Microsoft.Extensions.DependencyInjection
{
public static class SecurityServiceRepositoryCollectionExtension
{
public static IServiceCollection AddSecurityServiceRepositories(this
IServiceCollection
services)
{
services.AddTransient<IUserRepository, UserRepository>();
return services;
}
}
}
this is my swagger class file, it adds and uses the basic swagger service
namespace Microsoft.Extensions.DependencyInjection
{
public static class SwaggerServiceExtension
{
public static IServiceCollection AddSwaggerService(this IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Sample API",
Version = "v1",
Description = "REST API for Sample "
});
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = #"JWT Authorization header using the Bearer scheme. \r\n\r\n
Enter 'Bearer' [space] and then your token in the text input below.
\r\n\r\nExample: 'Bearer 12345abcdef'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header
},
new List<string>()
}
});
});
return services;
}
public static IApplicationBuilder UseSwaggerService(this IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample Api V1");
});
return app;
}
}
}
This is my controller, I have tried the both attribute
[Route("api/[controller]")]
[ApiController]
public class UserController : SecuredRepositoryController<IUserRepository>
{
public UserController(IUserRepository repository) : base(repository) { }
[HttpPost("register-user")]
// [Route("register-user")] I also tried this routing
[AllowAnonymous]
[ProducesResponseType(typeof(User), 200)]
public async Task<IActionResult> AddNewUser([FromBody] User user)
{
try
{
var result = await this.Repository.RegisterUser(user);
return Ok(result);
}
catch (Exception ex)
{
return StatusCode(500, ex.Message);
}
}
}
This is coming on swagger UI instead of Controller, check the screenshot
In my case I had to reference the project to the Host project which contains the Program
I encountered this error when creating a new ASP.NET core web api project, but forgot to check "Use controllers (uncheck to use minimal APIs)" in Visual Studio. Recreating the project with that box checked solved the issue for me.

Why is ASP.NET Core setting cache-control headers on error responses?

startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
services.AddResponseCaching();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors();
app.UseResponseCaching();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
TestController.cs
[ApiController]
public class TestController : ControllerBase
{
[ResponseCache(Duration = 60)]
[HttpGet("/fail")]
public IActionResult Fail()
{
return BadRequest();
}
}
When I hit the /fail endpoint, it returns a 400 status as expected but it has a Cache-Control header of public,max-age=60 because of the ResponseCache attribute on the action method. According to the docs,
Response Caching Middleware only caches server responses that result in a 200 (OK) status code. Any other responses, including error pages, are ignored by the middleware.
How can I stop error responses (or any non-200 response) from being cached?
The ResponseCacheFilter (source) is the action filter that actually sets the Cache-Control headers, but it doesn't take into account status codes, so it's not actually possible to do what I wanted.
Instead, I wrote my own action filter:
public class CacheControlAttribute : ActionFilterAttribute
{
public int Duration { get; set; } = 0;
public override void OnActionExecuted(ActionExecutedContext context)
{
if (ResultIsSuccess(context.Result))
{
SetCacheControlHeaders(context.HttpContext.Response);
}
}
private bool ResultIsSuccess(IActionResult result)
{
return result is IStatusCodeActionResult statusCodeActionResult && statusCodeActionResult.StatusCode is >= 200 and < 300;
}
private void SetCacheControlHeaders(HttpResponse response)
{
response.Headers[HeaderNames.CacheControl] = $"public,max-age={Duration}";
}
}
You can use it like this:
[ApiController]
public class TestController : ControllerBase
{
[CacheControl(Duration = 60)]
[HttpGet("/fail")]
public IActionResult Fail()
{
return BadRequest();
}
}
It will only set the Cache-Control headers on success status codes (>= 200 and < 300).
Also, no need for the app.UseResponseCaching() middleware in either case, as it doesn't control the Cache-Control headers; it just reads them (as might be set by the ResponseCache attribute), and caches cacheable responses to implement server-side caching.

SignalR Hub in C# class

Please tell me how I can use SignalR in not controller class.
I'm using AspNetCore.SignalR 1.0.2.
For example my Hub:
public class EntryPointHub : Hub
{
public async Task Sended(string data)
{
await this.Clients.All.SendAsync("Send", data);
}
}
In my job class (hangfire) SignalR doesn't work, my frontend not recieved messages.
public class UpdateJob
{
private readonly IHubContext<EntryPointHub> _hubContext;
public UpdateJob(IHubContext<EntryPointHub> hubContext)
{
_hubContext = hubContext;
}
public void Run()
{
_hubContext.Clients.All.SendAsync("Send", "12321");
}
}
But it In my controller works well.
...
public class SimpleController: Controller
{
private readonly IHubContext<EntryPointHub> _hubContext;
public SimpleController(IHubContext<EntryPointHub> hubContext)
{
_hubContext = hubContext;
}
[HttpGet("sendtoall/{message}")]
public void SendToAll(string message)
{
_hubContext.Clients.All.SendAsync("Send", message);
}
}
I think you are missing .net core DI mechanism for your Job Class. In Startup.cs file add that like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddScoped<UpdateJob>();
}
public void Configure(IApplicationBuilder app)
{
app.UseSignalR(routes =>
{
routes.MapHub<EntryPointHub>("ephub");
});
}
Then you need to install signalr-client for client end and calling like below in js file.
let connection = new signalR.HubConnection('/ephub');
connection.on('send', data => {
var DisplayMessagesDiv = document.getElementById("DisplayMessages");
DisplayMessagesDiv.innerHTML += "<br/>" + data;
});
Hope this will help you.
Solved: Thank for comments, I implement JobActivator and send to activator constructor ServiceProvider like this (in Startup.Configure):
IServiceProvider serviceProvider = app.ApplicationServices.GetService<IServiceProvider>();
GlobalConfiguration.Configuration
.UseActivator(new HangfireActivator(serviceProvider));
And add in ConfigureServices:
services.AddTransient<UpdateJob>();

Swagger 500 error in web api

When I access the swagger url: http//localhost:50505/swagger/index. I got the 500 error.
Please help me to figure out.
namespace BandwidthRestriction.Controllers
{
[Route("api/[controller]")]
public class BandwidthController : Controller
{
private SettingDbContext _context;
private readonly ISettingRespository _settingRespository;
public BandwidthController(ISettingRespository settingRespository)
{
_settingRespository = settingRespository;
}
public BandwidthController(SettingDbContext context)
{
_context = context;
}
// GET: api/Bandwidth
[HttpGet]
public IEnumerable<Setting> GetSettings()
{
return _settingRespository.GetAllSettings();
}
// GET: api/Bandwidth/GetTotalBandwidth/163
[HttpGet("{facilityId}", Name = "GetTotalBandwidth")]
public IActionResult GetTotalBandwidth([FromRoute] int facilityId)
{
// ...
return Ok(setting.TotalBandwidth);
}
// GET: api/Bandwidth/GetAvailableBandwidth/163
[HttpGet("{facilityId}", Name = "GetAvailableBandwidth")]
public IActionResult GetAvailableBandwidth([FromRoute] int facilityId)
{
// ...
var availableBandwidth = setting.TotalBandwidth - setting.BandwidthUsage;
return Ok(availableBandwidth);
}
// PUT: api/Bandwidth/UpdateBandwidthChangeHangup/163/10
[HttpPut]
public void UpdateBandwidthChangeHangup([FromRoute] int facilityId, [FromRoute]int bandwidth)
{
_settingRespository.UpdateBandwidthHangup(facilityId, bandwidth);
}
// PUT: api/Bandwidth/UpdateBandwidthChangeOffhook/163/10
[HttpPut]
public void UpdateBandwidthChangeOffhook([FromRoute] int facilityId, [FromRoute] int bandwidth)
{
_settingRespository.UpdateBandwidthOffhook(facilityId, bandwidth);
}
// POST: api/Bandwidth/PostSetting/163/20
[HttpPost]
public bool PostSetting([FromRoute] int facilityId, [FromRoute]int bandwidth)
{
//
return false;
}
}
The corresponding configuration code in Startup.cs is
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<SettingDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddMvc();
services.AddSwaggerGen();
services.ConfigureSwaggerDocument(options =>
{
options.SingleApiVersion(new Info
{
Version = "v1",
Title = "Bandwidth Restriction",
Description = "Api for Bandwidth Restriction",
TermsOfService = "None"
});
// options.OperationFilter(new Swashbuckle.SwaggerGen.XmlComments.ApplyXmlActionComments(pathToDoc));
});
services.ConfigureSwaggerSchema(options =>
{
options.DescribeAllEnumsAsStrings = true;
//options.ModelFilter(new Swashbuckle.SwaggerGen.XmlComments.ApplyXmlTypeComments(pathToDoc));
});
// Add application services.
services.AddTransient<ISettingRespository, SettingRespository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{facilityId?}");
routes.MapRoute(
name: "",
template: "{controller}/{action}/{facilityId}/{bandwidth}");
});
app.UseSwaggerGen();
app.UseSwaggerUi();
}
In firefox: the error is unable to load swagger ui
Your route attributes are wrong. The routes for GetAvailableBandWidth and GetTotalBandWidth are both mapped to the route api/bandwidth/{facilityId} and not, as your comments suggests, to api/Bandwidth/GetAvailableBandwidth/{facilityId} and api/Bandwidth/GetTotalBandwidth/{facilityId}. The same goes, sort of, for your put methods.
When you register two identical routes, one will fail and throws an exception. Hence the http status code 500.
You can fix it like this:
// GET: api/Bandwidth/GetTotalBandwidth/163
[HttpGet("GetTotalBandwidth/{facilityId}", Name = "GetTotalBandwidth")]
public IActionResult GetTotalBandwidth(int facilityId)
{
// ...
return Ok(setting.TotalBandwidth);
}
// GET: api/Bandwidth/GetAvailableBandwidth/163
[HttpGet("GetAvailableBandwidth/{facilityId}", Name = "GetAvailableBandwidth")]
public IActionResult GetAvailableBandwidth(int facilityId)
{
// ...
var availableBandwidth = setting.TotalBandwidth - setting.BandwidthUsage;
return Ok(availableBandwidth);
}
// PUT: api/Bandwidth/UpdateBandwidthChangeHangup/163/10
[HttpPut("UpdateBandwidthChangeHangup/{facilityId}/{bandwidth}")]
public void UpdateBandwidthChangeHangup(int facilityId, int bandwidth)
{
_settingRespository.UpdateBandwidthHangup(facilityId, bandwidth);
}
// PUT: api/Bandwidth/UpdateBandwidthChangeOffhook/163/10
[HttpPut("UpdateBandwidthChangeOffhook/{facilityId}/{bandwidth}")]
public void UpdateBandwidthChangeOffhook(int facilityId, int bandwidth)
{
_settingRespository.UpdateBandwidthOffhook(facilityId, bandwidth);
}
Please note I removed the [FromRoute] attributes because they are not necessary.

Resources