ASP.NET Core WebAPI 404 error - asp.net-web-api

I create a Web Api in asp.net core this the content of Api:
[Route("api/[controller]")]
public class BlogController : Controller
{
public IContext _context { get; set; }
public BlogController(IContext ctx)
{
_context = ctx;
}
[HttpGet]
[Route("api/Blog/GetAllBlog")]
public List<Blog> GetAllBlog()
{
return _context.Blogs.ToList();
}
}
as i know in ASp.net Core (WebApi Template) we don't need any configuration like registration Route, which we need in Asp.net Mvc 5.3 and older.
So when i try to call the GetAllBlog by browser or Postman, by this url http://localhost:14742/api/Blog/GetAllBlog , it gets me 404 error, what is problem?

You have already included the api/[controller] route at the top of the controller class so you don't need to include it again while defining route for accessing method.
In essence, change the Route to api/Blog/GetAllBlog to GetAllBlog. Your code should look like this:
[Route("api/[controller]")]
public class BlogController : Controller
{
public IContext _context { get; set; }
public BlogController(IContext ctx)
{
_context = ctx;
}
[HttpGet]
[Route("GetAllBlog")]
public List<Blog> GetAllBlog()
{
return _context.Blogs.ToList();
}
[HttpGet]
[Route("GetOldBlogs")]
public List<Blog> GetOldBlogs()
{
return _context.Blogs.Where(x => x.CreationDate <= DateTime.Now.AddYears(-2)).ToList();
}
}
You also need to have different route names for methods.
Hope this helps.

Related

ASP.NET Core Web API : dependency injection based on runtime parameter value

I am working on an ASP.NET Core Web API application. My API will accept a country name as one of the input parameter from request body.
Due to nature of the application, we have country wise database with same schema. I have created DbContext for one of the databases and want to initialize the DbContext by the passing the connection string based on input request parameter value.
I have created factory method to return the needed database context based on the parameter passed to the factory method. However, the challenge I am facing is, while initializing the factory class as DI from controller, object of factory class is instantiated before the controller action is called. Hence, parameter value in factory method is empty.
How can I pass a parameter in runtime to initialize an object using dependency injection?
Here is the code...
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyDBContext : DbContext
{
public MyDBContext(DbContextOptions<MyDBContext> options)
: base(options)
{
}
public virtual DbSet<Student> Students { get; set; }
}
public interface IDbContextFactory
{
public MyDBContext GetDbContext(string
connectionString);
}
public class DbContextFactory : IDbContextFactory
{
public MyDBContext GetDbContext(string connectionString)
{
MyDBContext context = null;
if (!string.IsNullOrWhiteSpace(connectionString))
{
DbContextOptionsBuilder<MyDBContext> _dbContextOptionsBuilder = new DbContextOptionsBuilder<MyDBContext>().UseSqlServer(connectionString);
context = new MyDBContext(_dbContextOptionsBuilder.Options);
}
return context;
}
}
public interface IRepository
{
Student GetData();
}
public class Repository : IRepository
{
private MyDBContext _context;
public Repository(IDbContextFactory dbContextFactory)
{
// Here I need connection string based on input parameter (country) from request to dynamically generate country specific connection string
string connectionString = string.Empty;
_context = dbContextFactory.GetDbContext(connectionString);
}
public Student GetData()
{
return _context.Students.FirstOrDefault();
}
}
public interface IServiceAgent
{
Student GetData();
}
public class ServiceAgent : IServiceAgent
{
IRepository _repository;
public ServiceAgent(IRepository repository)
{
_repository = repository;
}
public Student GetData()
{
return _repository.GetData();
}
}
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
private readonly IServiceAgent _serviceAgent;
public HomeController(IServiceAgent serviceAgent)
{
_serviceAgent = serviceAgent;
}
[HttpGet]
public Student Get(string country)
{
return _serviceAgent.GetData();
}
}
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.AddScoped<IServiceAgent, ServiceAgent>();
services.AddScoped<IRepository, Repository>();
services.AddScoped<IDbContextFactory, DbContextFactory>();
services.AddScoped<DetermineCountryFilter>();
}
// 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();
});
}
}

swagger swashbuckle does not support nested class as action method parameter

I am using asp.net 5
I have two model class, which are nested, both of the inner class are named Command
public class EditModel
{
public class Command
{
public int Id { get; set; }
public string Info { get; set; }
}
}
and
public class CreateModel
{
public class Command
{
public int Id { get; set; }
public string Info { get; set; }
}
}
In my Controller class has two methods
[HttpPost]
public IActionResult PutData(CreateModel.Command model)
{
return Ok();
}
[HttpPut]
public IActionResult PostData(EditModel.Command model)
{
return Ok();
}
Since for both Put and Post's query I am using nested class both name Command, Swagger will return the following error
An unhandled exception has occurred while executing the request.
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Conflicting method/path combination "PUT Test" for actions -
TestSwagger.Controllers.TestController.PutData
(TestSwagger),TestSwagger.Controllers.TestController.PostData
(TestSwagger). Actions require a unique method/path combination for
Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable1 apiDescriptions, SchemaRepository schemaRepository) at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable1
apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String
documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext
httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext
context)
Swagger will work, if I change one of the Command model name to something different.
Yet, I believe this nested class model name is legit and should work with swagger also. If there a way to work around this. Thanks
By adding c.CustomSchemaIds(x => x.FullName);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestSwagger", Version = "v1" });
c.CustomSchemaIds(x => x.FullName);
});
solved the schemaId conflict. Thanks to this question

Custom action filter (with dependencies) not working in ASP.NET Core 3.1 Web API

I'm having some difficulties getting a custom action filter to work in ASP.NET Core 3.1 Web API. I've followed this SO, as well as the Microsoft docs, but it's not working. I've created a simple filter (note: I need Dependency Injection);
public class LogFilterAttribute : ActionFilterAttribute, IFilterMetadata
{
private readonly ILogger<LogFilterAttribute> _logger;
public LogFilterAttribute(ILogger<LogFilterAttribute> logger)
{
_logger = logger;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
_logger.LogWarning("test");
base.OnActionExecuting(actionContext);
}
}
Notes:
ActionFilterAttribute is from System.Web.Http.Filters namespace.
I implemented IFilterMetadata (which is just a marker interface) as this seems to be required by ServiceFilter and TypeFilter.
I'm registering this in ConfigureServices of Startup.cs as follows:
services.AddScoped<LogFilterAttribute>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
and then applying this in my Web API controller as follows:
[ApiVersion("1.0")]
[ApiController]
[Route("v{version:apiVersion}/resources/{id}")]
public class ResourceController : ControllerBase
{
private readonly ILogger<ResourceController> _logger;
public ResourceController(ILogger<ResourceController> logger)
{
_logger = logger;
}
[HttpGet]
[ServiceFilter(typeof(LogFilterAttribute))]
public async Task<IActionResult> Get([FromRoute(Name = "id")] string id)
{
_logger.LogInformation($"{typeof(ResourceController)}.{nameof(Get)}");
return Ok();
}
}
I've tried with both ServiceFilter and TypeFilter, but to no avail - it just skips the break-point in filter and goes straight to my route logic. What am I doing wrong?
Try implementing IActionFilter in place of ActionFilterAttribute
In the end I solved the issue by implementing IAsyncActionFilter and inheriting from Attribute as follows:
public class LogFilterActionFilterAttribute : Attribute, IAsyncActionFilter
{
public LogFilterActionFilterAttribute(...)
{
...
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
...
}
}
I also override TypeFilterAttribute as follows:
public class LogFilterAttribute : TypeFilterAttribute
{
public LogFilterAttribute (...) : base(typeof(LogFilterActionFilterAttribute))
{
Arguments = new object[] { ... };
}
}
So that I can decorate on controllers/routes as follows:
[ApiVersion("1.0")]
[ApiController]
[Route("v{version:apiVersion}/resources/{id}")]
public class ResourceController : ControllerBase
{
private readonly ILogger<ResourceController> _logger;
public ResourceController(ILogger<ResourceController> logger)
{
_logger = logger;
}
[HttpGet]
[LogFilter(...)]
public async Task<IActionResult> Get([FromRoute(Name = "id")] string id)
{
_logger.LogInformation($"{typeof(ResourceController)}.{nameof(Get)}");
return Ok();
}
}
In StartUp.cs, Add the filter in MVC pipeline.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(typeof(LogFilterAttribute));
});
}
You need to use this on the controller/method as you're using a type filter, isn't the logger already scoped within the configuration? if so you need a type filter
[TypeFilter(typeof(LogFilterAttribute))]
For my use, I don't need to add IFilterMetadata

How multiple Get function could be there in a web api controller

just see the scenario
public class CustomerController : ApiController
{
public IEnumerable<Customer> GetCustomersByID(int id)
{
}
public IEnumerable<Customer> GetCustomersByName(string name)
{
}
public IEnumerable<Customer> GetCustomersByEmail(string strEmail)
{
}
}
now tell me what i need to do as a result end user can call three get action by their name. how to handle this situation. thanks
You can set route for each method. Such as:
[Route("GetById/{id}")]
public IEnumerable<Customer> GetCustomersByID(int id)
{
}
You can call it getbyid/3. More details web api routing
Also there is a question for this issue.

How to ensure ASP.net Web API controller's parameter is not null?

I created a ASP.net Web API controller like that:
public class UsersController : ApiController
{
//...
public void Put([FromBody]User_API user, long UpdateTicks)
{
user.UpdateTicks = UpdateTicks;
//...
}
}
The "user" parameter will be null if the client does not provide correct arguments. Can I make a global filter to check every parameter like this, and will return a 400 message if any error occurs.
Finally, I got the solution:
public class ModelValidateFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.Any(v => v.Value==null))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
}
And...
//In Application_Start()
GlobalConfiguration.Configuration.Filters.Add(new ModelValidateFilterAttribute());

Resources