I'm starting learning asp.net core, and I have problems with understanding some basic patterns. Can I use ApiController inside PageController?
For example
//Home controller
public async Task<IActionResult> Index()
{
var artists = await _artistController.GetAll();
var t = artists.GetValue();
var indexModel = new Models.Pages.IndexModel
{
Artists = artists.GetValue() //Extension method
};
return View(indexModel);
}
//ArtistRestController
[HttpGet]
public async Task<ActionResult<IEnumerable<Artist>>> GetAll()
{
try
{
return Ok(await _repository.GetAll());
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return BadRequest(ex.Message);
}
}
It works, but is is ok by design? Maybe I should directly call _repository.GetAll() and do not use other controller?
First, you should avoid using one Controller in another Controller. For Controller, it is used to handle http request from client. It is unreasonable to use it as a class even through it is a class.
Second, for combining ApiController and PageController will make your application unstatable and too coupling. If you change anything later in ArtistRestController.GetAll will make HomeController broken. And, for your current code, if there are some exception in PageController, your ApiController will be down.
You should inject repository to query the data.
Related
With the introduction of the Apicontroller attribute in asp.net core 2.1, I wonder how do I get the api and normal methods to work in the same controller.
[Route("api/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> SaveOrder(SaveOrderModel model)
{
//...
}
public async Task<IActionResult> CustomerOrders()
{
if (!User.IsInRole("Customer"))
return Challenge();
var customer = await _workContext.CurrentCustomer();
var model = await orderModelFactory.PrepareCustomerOrderListModel();
return View(model);
}
}
I can call post method /api/order/saveorder but cannot run the https://example.com/order/customerorders.
It shows an exceptions: InvalidOperationException: Action
'.CustomerOrders ' does not have an attribute route. Action methods on
controllers annotated with ApiControllerAttribute must be attribute
routed.
If I remove [ApiController] and [Route("api/[controller]")] on the controller level and instead put on the method level, then it surely works. still don't know if there's any better hybrid solution for these methods as i want to use this new ApiController feature.
[Route("/api/controller/saveorder")]
public async Task<IActionResult> SaveOrder(SaveOrderModel model)
Any input is greatly appreciated.
You are saying, that you cannot call https://example.com/order/customerorders. In your [Route("api/[controller]")] you define, that all Methods inside this controller will be available at https://example.com/api/order/.
So to call your method, you need to call https://example.com/api/order/customerorders.
If you want to stay with https://example.com/order/customerorders, you need to put the [Route] attributes at your methods:
[ApiController]
public class OrderController : ControllerBase
{
[HttpPost("api/order")]
public async Task<IActionResult> SaveOrder(SaveOrderModel model)
{
...
}
[HttpGet("order/customerorders")]
public async Task<IActionResult> CustomerOrders()
{
if (!User.IsInRole("Customer"))
return Challenge();
var customer = await _workContext.CurrentCustomer();
var model = await orderModelFactory.PrepareCustomerOrderListModel();
return View(model);
}
}
Is it best practice to put each distinct Get method in its own controller class, or is it perfectly fine to have multiple (related and non-related) API methods in the same class, if the methods are very simple and uncomplicated.
E.g. these two API methods work fine in the same controller class, but would they be better off in their own class?
If so why?
public class TestController : ApiController
{
[HttpGet]
[Route("api/test/ping")]
public IHttpActionResult Ping()
{
try
{
return Ok("HELLO");
}
catch (Exception ex)
{
return Content(HttpStatusCode.InternalServerError, ex.Message);
}
}
[HttpGet]
[Route("api/test/echo/{message}")]
public IHttpActionResult Echo(string message)
{
try
{
return Ok(message);
}
catch (Exception ex)
{
return Content(HttpStatusCode.InternalServerError, ex.Message);
}
}
}
There is nothing stopping you from having multiple actions in a controller once their routes are distinct and do not cause route conflicts in the current or other controllers.
Take your provided example. You can take advantage of route prefixes for the controller to help with the organizing of similar routes
[RoutePrefix("api/test")]
public class TestController : ApiController {
//GET api/test/ping
[HttpGet] [Route("ping")]
public IHttpActionResult Ping() {
return Ok("HELLO");
}
//GET api/test/echo/hello%20world
[HttpGet] [Route("echo/{message}")]
public IHttpActionResult Echo(string message) {
if(message == null)
return BadRequest();
return Ok(message);
}
}
Personally I would put related API actions that work that do related work together in 1 single controller class.
In your given example it would be fine to put them together. Another example, say you have a Controller that handles all actions on a User model (Please note not entirely valid code, but hopefully you get the point):
[RoutePrefix("api/users")]
public class UserController: ApiController
{
[HttpGet]
public IHttpActionResult GetUsers()
{
// GET all users.
}
[HttpGet]
[Route("{id}")]
public IHttpActionResult GetUserById(int id)
{
// GET user by ID
}
[HttpPost]
public IHttpActionResult CreateUser()
{
// Create User
}
[HttpPut]
[Route("{id}")]
public IHttpActionResult UpdateUser()
{
// Update User
}
}
As you can see, all these actions work on the User model, so they fit together in a Controller class.
I would like to restrict my Web API endpoints to certain HTTP methods, such as GET and POST. I have searched the internet and I found out that you can add either [HttpGet] or [HttpPost] above your method as follows:
[HttpPost]
public ActionResult Login(string userName, string password) {
// do login stuff
return View();
}
Now I want to test if the example above with [HttpPost] really works so I use postman to send a HTTP request to my Web API. I fill in the URI and set the method to GET. The response I get is as follows:
{
"message": "The requested resource does not support http method 'POST'."
}
I'm able to verify that adding [HttpPost] prevents me from using HTTP GET requests.
What I would like to do now is log the event whenever an user tries to sent GET requests when the application is expecting POST, and vice versa. I could implement something for every single method but this would take a lot of time and it wouldn't be easy to make changes once it's been implemented. So I would like to filter it globally or something.
For example something like:
class MethodRequestFilter : SomeGlobalMethodFilter
{
public override void Filter(SomeRequestContext requestContext)
{
if (usedRequestMethod.isNotValid == true)
{
//implementation for logging
}
}
}
But ofcourse I haven't been able to find this yet within the libraries of .Net. How can I log the event globally whenever a user tries to make a request that isn't a supported method?
Greetings,
Damien.
One way is to using common base controller, to implement you need to add one base controller which would inherited from ApiController
public class BaseController : ApiController
{
public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
try
{
HttpResponseMessage response = await base.ExecuteAsync(controllerContext, cancellationToken);
if (!response.IsSuccessStatusCode) // or if(response.StatusCode == HttpStatusCode.BadRequest)
{
//log here
}
return response;
}
catch(Exception ex)
{
return await InternalServerError(ex).ExecuteAsync(cancellationToken);
}
}
}
Now, let's assume that you're having ValuesController and Login method and it supports only POST, here your all other controllers inherit from BaseController instead ApiController
public class ValuesController : BaseController
{
[HttpPost]
public void Login([FromBody]string value)
{
}
}
So, once you call your login method, it'll call BaseController method first and you will get response there.
Hope this helps!
Thanks to the user div I was able to solve my problem by using a base controller that implements logging. These are the steps that I had to take:
Create a new controller class called BaseController and inherit ApiController:
Override the ExecuteAsync method from ApiController:
Add an implementation for logging in the catch clause
Inherit the new BaseController in every controller class that you would like to have logging functionality.
The code that I used in my implementation:
public class BaseController : ApiController
{
public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
try
{
HttpResponseMessage response = await base.ExecuteAsync(controllerContext, cancellationToken);
return response;
}
catch (HttpResponseException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.MethodNotAllowed)
{
//Logging implementation
}
return Request.CreateErrorResponse(ex.Response.StatusCode, ex.Message);
}
}
}
If there is any way to make my code better, please let me know :)
Im a beginner with Web api and Im trying to setup a simple owin selfhosted service that Im trying out.
I've been searching stackoverflow and other places for a while now, but I cant seem to find anything obviously wrong.
The problem I have is that I get a bad request response everytime I try to call my service.
The controller looks like this:
[RoutePrefix("api/ndtdocument")]
public class NDTDocumentsController : ApiController, INDTDocumentsController
{
[HttpGet]
public IHttpActionResult Get()
{
var document = Program.NDTServerSession.GetNextNDTDocument(DateTime.Today);
if (document == null)
return null;
return Ok(document);
}
[Route("")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
{
try
{
Program.NDTServerSession.AddNDTDocument(ndtDocument);
return Ok();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
}
And the client looks like this:
static void Main(string[] args)
{
AddNDTDocument(#"C:\Testing.txt");
}
private static void AddNDTDocument(string centralserverPath)
{
var client = GetServerClient();
NDTDocument document = new NDTDocument();
var response = client.PostAsJsonAsync("ndtdocument", document).Result;
}
static HttpClient GetServerClient()
{
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:9000/api/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
I can see when I debug it that the request uri is infact http://localhost:9000/api/ndtdocument
But the response is allways bad request and I have a breakpoint in the controller and it is never invoked.
Everytime I try to do something with web apis I Always run into some weird (but simple problem).
Any thoughts?
Thanks!
Web API will decide your route based on your method names. Since you have added [RoutePrefix("api/ndtdocument")] on class level this will be the route to your controller. When web api looks for an action it will match on method names, so in your case your actual route would be http://localhost:9000/api/ndtdocument/post.
When trying to decide what http method that a specific action requires web api will check your method names and methods starting with post will be http post, get will be http get etc.
So lets say we would instead call our method PostData, for starters we could remove the [HttpPost] attribute. Our route would now be http://localhost:9000/api/ndtdocument/postdata. Let's now say that we want our path to be just /data. We would then first rename our method to Data, but now web api does not know what http method we want to invoke this method with, thats why we add the [HttpPost] attribute.
Edit after reading your comment
[Route("{id:int}")]
public IHttpActionResult Get(int id)
[Route("")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
Okey, after nearly going seriously insane. I found the problem.
I forgot to reference webapi.webhost and then system.web.
After this Everything worked like a charm.
You must use route tags and call this way http://localhost:9000/api/get or http://localhost:9000/api/post
[RoutePrefix("api/ndtdocument")]
public class NDTDocumentsController : ApiController, INDTDocumentsController
{
[HttpGet]
[Route("get")]
public IHttpActionResult Get()
{
var document = Program.NDTServerSession.GetNextNDTDocument(DateTime.Today);
if (document == null)
return null;
return Ok(document);
}
[HttpPost]
[Route("post")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
{
try
{
Program.NDTServerSession.AddNDTDocument(ndtDocument);
return Ok();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
}
for more infromation pls check this link
I have a OData web api using ADO.NET Framework in which the controller action is somehow not being reached.
The API correctly receives the HTTP request and parses it to go to the correct action but the action is not reached.
And in return the chrome browser shows the authentication window.
I have been debugging so long but cannot figure out how to solve this.
The controller is (stripped version):
public class DataController : ODataController
{
private readonly DataModel DataAccessModel = new DataModel();
public DataController()
{
.......
}
[HttpGet, EnableQuery]
public IQueryable<Record> GetRecord(ODataQueryOptions<Record> options)
{
try
{
IQueryable<ActivationRequestLog> result;
try
{
result = DataAccessModel.Recordss;
}
catch (Exception ex)
{
......
}
}
}
}
Can you show how the controller has been registered in the WebApiConfig class?
If you're using the ODataConventionModelBuilder, then you have to follow certain naming conventions for controllers of entity sets.
e.g. If I register an Airlines entity set of type Airline
builder.EntitySet<Airline>("Airlines");
....then by default/convention I need to implement
public class AirlinesController : ODataController<Airline>
{
[EnableQuery]
public IQueryable<Airline> Get()
{
DB db = Request.GetContext();
return db.Airlines();
}
}