Added an API controller to the project and it does not work. I get 404.
[Route("api/hlth")]
[ApiController]
public class hlth : ControllerBase
{
// GET: api/<hlth>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<hlth>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
}
Turns out I need to add
app.MapControllers();
that for some reason is not included in the default project configuration.
Related
I have added an ASP.NET Web API to an ASP.NET MVC 5 web application project developed in Visual Studio 2019.
WebApiConfig:
public class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Global.asax.cs:
public class MvcApplication : HttpApplication {
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(WebApiConfig.Register);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
Api Controller Method:
// GET: api/Web
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
When I hit https://localhost:44324/api/web the browser gives this error:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /api/web
In MVC 5 routing you can try using attributing routing to overcome this error.
The solution is shown below.
API Controller Method:
//write this above the class and below namespace
[ApiController]
[Route("[controller]")]
// GET: api/Web
[HttpGet]
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
When you hit https://localhost:44324/api/web but here "Web" is controller id it's not then you have to mention that first and then the method the browser will not give you an error:
Filename: DemoController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MVCEntityFramework.Controllers.Api
{
public class DemoController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody] string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
}
ScreenShot 1: https://i.stack.imgur.com/lDQ4O.png
ScreenShot 2: https://i.stack.imgur.com/ObG6W.png
Path Not Working:
https://localhost:44310/api/demo/get/2
https://localhost:44310/api/democontroller/get/2
Response:
HTTP Error 404.0 - Not Found
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
It'll work by convention so if you need to call Get by id just use https://localhost:44310/api/demo/2 without action name but you need to specify verb HttpGet if you need to call Post also you will call https://localhost:44310/api/demo with specify verb HttpPost
This is how to call post action using postman for example
or add [Route("[controller]")] attribute to class like
[Route("[controller]")]
public class DemoController : ApiController
{
// GET api/<controller>
[Route("Get")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
so in this case you can add controller name to your endpoint like https://localhost:44310/api/demo/get/2
I can use this attribute to custom route aspnetcore api controllers:
[Route("test")]
but oData won't recognize it.
How can I fix this?
As Requested here is all the code:
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(mvcOptions => mvcOptions.EnableEndpointRouting = false);
services.AddOData();
}
// 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.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.Expand().Filter().OrderBy().Select().SkipToken();
routeBuilder.MapODataServiceRoute("odata", "odata", GetEdmModel());
});
}
private IEdmModel GetEdmModel()
{
var edmBuilder = new ODataConventionModelBuilder();
edmBuilder.EntitySet<DivUser>("Users");
edmBuilder.EntitySet<DivClaim>("Claims");
edmBuilder.EntitySet<DivUserRole>("UserRoles");
edmBuilder.EntitySet<DivUserType>("UserTypes");
return edmBuilder.GetEdmModel();
}
}
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
[EnableQuery]
public IEnumerable<DivUser> Get()
{
IEnumerable<DivUser> users;
using (var context = new DivDbContext())
{
users = context.Users.Include(user => user.UserClaims).ThenInclude(userClaim => userClaim.Claim).ToList();
}
return users;
}
[HttpGet]
[EnableQuery]
[Route("test")]
public IEnumerable<DivUser> Test()
{
IEnumerable<DivUser> users;
using (var context = new DivDbContext())
{
users = context.Users.Include(user => user.UserClaims).ToList();
}
return users;
}
}
https://localhost:44354/users WORKS
https://localhost:44354/users/test WORKS
https://localhost:44354/odata/users WORKS
https://localhost:44354/odata/users/test DOES NOT
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].
I am using ASP.NET Core 2.0
At Startup.cs I have
services.AddDbContext<MailDBServicesContext>(optionsSqLite =>
{
optionsSqLite.UseSqlite("Data Source=Mail.db");
});
I have created a model and a DbContext where DbContext is:
public class MailDBServicesContext : DbContext
{
public MailDBServicesContext(DbContextOptions<MailDBServicesContext> options)
: base(options)
{
}
public DbSet<MailCountSentErrorMails> DbSetMailCountSentErrorMails { get; set; }
}
from a Class helper I need to pass DbContextOptions and my question is how can I tell to use the options from the Startup.cs ConfigureServices method
using (var db = new MailDBServicesContext())
{
}
It should be enough to simply inject MailDBServicesContext into your controller or a service class, for example.
public class SomeDataService
{
private readonly MailDBServicesContext _dbContext;
public SomeDataService(MailDBServicesContext dbContext)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
public async Task AddMailCounts()
{
_dbContext.DbSetMailCountSentErrorMails
.Add(new MailCountSentErrorMails { CountSentMails = 55 });
await _dbContext.SaveChangesAsync();
}
}
Other DB context configuration options are defined in Configuring a DbContext on MSDN.
Update
Make sure to register your service in DI, i.e. ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISomeDataService, SomeDataService>();
services.AddDbContext<MailDBServicesContext>(optionsSqLite =>
{
optionsSqLite.UseSqlite("Data Source=Mail.db");
});
services.AddMvc();
}
Then make a call to AddMailCounts() in your controller.
public class HomeController : Controller
{
private readonly ISomeDataService _dataService;
public HomeController(ISomeDataService dataService)
{
_dataService = dataService ?? throw new ArgumentNullException(nameof(dataService));
}
public IActionResult Index()
{
_dataService.AddMailCounts();
return View();
}
}
Now every time you load homepage, a record is inserted into DbSetMailCountSentErrorMails table.
You can find working solution on my GitHub.