Webapi and normal methods in the same controller? - c#

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);
}
}

Related

Cannot return HTML from a Controller

My code fails to return HTML from a Controller. The browser returns HTTP ERROR 500 - "This page does not work".
The Controller is written in .net core 3.1. Here is the code:
[ApiController]
[Route("")]
public class MyController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<ContentResult>> GetInformation()
{
string s = #"<!DOCTYPE html><html><head><title>.....";
return base.Content(s, "text/html");
}
}
However, returning a plain string works: (using string instead of ContentResult. However, this string is not interpreted as HTML and is written directly to the browser)
[ApiController]
[Route("")]
public class MyController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<string>> GetInformation()
{
string s = #"something";
return s;
}
}
public async Task<ActionResult<ContentResult>> GetInformation()
Because you declare that the action returns an ActionResult<ContentResult>, I expect that the ASP.NET Core serialisation process attempts to serialise the ContentResult you return as a JSON object. This is neither intended nor something the framework can manage.
Controller action return types in ASP.NET Core web API goes into the details, but a more typical method signature for your scenario would look like this:
public async Task<IActionResult> GetInformation()
You could also specify ContentResult, which implements IActionResult, if you'd prefer to do that:
public async Task<ContentResult> GetInformation()
IActionResult allows an action to return different responses in different situations, using e.g. BadRequest, Content, or Ok.

Why is index method not called when action name is not specified

I have a simple ASP.NET Core API with the following controller:
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
public IActionResult Index()
{
return Ok("index");
}
public IActionResult Get()
{
return Ok("get");
}
}
When I hit URL:
"http://localhost:6001/api/home"
With a GET request, I get the following error:
"The request matched multiple endpoints."
Normally, if I don't specify an action name, shouldn't it call the Index() method?
In ASP.NET Core API, action methods are matched by a combination of HTTP verb used as attribute on the method (which is HttpGet by default if you don't add any) and the route parameter used with it. In that sense both your action methods looks similar to the routing system. It sees two HttpGet method with no route parameter. That's why both method matches the request -
"http://localhost:6001/api/home"
making the routing system confused which to select. That's exactly what the error message is saying.
You need to make your non-default action method more specific, so that the routing mechanism can differentiate between the two. Something like -
[HttpGet("data")]
public IActionResult Get()
{
return Ok("get");
}
Now your Index method will be matched as default action method with the above URL.
Both of your endpoints are identical. Use routes to differentiate them. For example:
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
[HttpGet]
public IActionResult Index()
{
return Ok("index");
}
[HttpGet, Route("data")]
public IActionResult Get()
{
return Ok("get");
}
}
This would open up:
http://localhost:6001/api/home/
and
http://localhost:6001/api/home/data
respectively using GET
You could also change the verb, for example use HttpGet for one and HttpPost for another.

Http get and post method without attribute name

I'm using .net Core 2.1 Web API. I add every method [HttpGet("....")] or [HttpPost("....")] like below. But, I don't want to write every method in every controller. I want to write only [HttpGet] or [HttpPost]. How can I achieve this?
Or, if this is not possible, can I do this like [HttpPost("[action]")] with taking automatically actionName?
I'm calling like this:
http://localhost:5000/api/University/GetUniversities
This working perfectly
namespace University.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UniversityController : ControllerBase
{
private readonly IUniversityService universityService;
public UniversityController(IUniversityService universityService)
{
this.universityService = universityService;
}
[HttpGet("GetUniversities")]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
[HttpGet("GetUniversityStatues")]
public async Task<ServiceResult> GetUniversityStatues()
{
return await universityService.GetUniversityStatues();
}
}
}
I tried this but is not working:
namespace University.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UniversityController : ControllerBase
{
private readonly IUniversityService universityService;
public UniversityController(IUniversityService universityService)
{
this.universityService = universityService;
}
[HttpGet]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
[HttpGet]
public async Task<ServiceResult> GetUniversityStatues()
{
return await universityService.GetUniversityStatues();
}
}
}
Web API like MVC has the convention over configuration, so if you didn't define the endpoint in the attribute, it will work but not as you expect. Generally Get method will be called like this api/[ControllerName] post method the same.
In your case, if you need to name your route the same name of your action you can write this attribute above your controller
[Route("[controller]/[action]")]
You can use just [HttpGet]
[HttpGet]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
And then send a get request to http://localhost:5000/api/University. It will work fine.
But your problem is you have more than one parameterless get methods. then it can't identify which get method you need to call.
So if you really want to use two parameterless get methods in same controller you have to decorate it like this,
[HttpGet("GetUniversities")]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
[HttpGet("GetUniversityStatues")]
public async Task<ServiceResult> GetUniversityStatues()
{
return await universityService.GetUniversityStatues();
}
Otherwise you have to move the second one to another controller
You can automate it a little by this
[HttpPost(nameof(GetUniversities))]
It's not required to pass the method name with Http Verb. You can use like this also:
[HttpGet]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
But you want to change the method name or route. You can something like this:
[HttpGet("", Name="Students"]
public async Task<ServiceResult> GetStudents()
{
return await universityService.GetStudents();
}

How to make valid route for {id}/visit?

I am new at asp.core , so I try to make valid route to {id}/visits
My code:
[Produces("application/json")]
[Route("/Users")]
public class UserController
{
[HttpGet]
[Route("{id}/visits")]
public async Task<IActionResult> GetUser([FromRoute] long id)
{
throw new NotImplementedException()
}
}
But, at route {id} generated method the same:
// GET: /Users/5
[HttpGet("{id}")]
public async Task<IActionResult> GetUser([FromRoute] long id)
{
return Ok(user);
}
How to make route /Users/5/visits nethod?
What parameters at GetUser should I add?
Name the methods differently and use constraints to avoid route conflicts:
[Produces("application/json")]
[RoutePrefix("Users")] // different attribute here and not starting /slash
public class UserController
{
// Gets a specific user
[HttpGet]
[Route("{id:long}")] // Matches GET Users/5
public async Task<IActionResult> GetUser([FromRoute] long id)
{
// do what needs to be done
}
// Gets all visits from a specific user
[HttpGet]
[Route("{id:long}/visits")] // Matches GET Users/5/visits
public async Task<IActionResult> GetUserVisits([FromRoute] long id) // method name different
{
// do what needs to be done
}
}

Where to put common code in view controller View functions?

In one of the controllers, every view has a fixed preprocessing. Is there a better way of doing this, instead of the below code; such that SomeFunctionAsync works without writing that line before return View() for all functions with return View() in this controller? I also have some ajax post functions.
public async Task<ActionResult> View1()
{
await SomeFunctionAsync();
return View();
}
public async Task<ActionResult> View2()
{
await SomeFunctionAsync();
return View();
}
In other words, at the end I want to be able to write the following with having the same effect
public async Task<ActionResult> View1()
{
return View();
}
public async Task<ActionResult> View2()
{
return View();
}
If Action Filter as suggested by Varun doesnt suits you, you can try another way.
Create a parent View of all the view. In the action method for your parent view. Call this Method SomeFunctionAsync(). Thus, parent view will be called for all of yours views and the required method will be executed
You can create a base class for your controller, and have have each view in your code call a generic method. I use GetView as a method name (or you could override the existing ones).
Like so:
public class MyControllerBase : Controller {
public Task<ActionResult> GetView() {
yourCommonMethod();
return View();
}
public Task<ActionResult> GetView(string viewName) {
yourCommonMethod();
return View(viewName);
}
public Task<ActionResult> GetView(object model) {
yourCommonMethod();
return View(model);
}
public Task<ActionResult> GetView(string viewName, object model) {
yourCommonMethod();
return View(viewName, model);
}
}
Then in your controller, inherit from that:
public class MyController : MyControllerBase {
public async Task<ActionResult> View1()
{
return GetView();
}
}
If the common method is the same for all controllers and has no controller-specific dependencies, it could be that simple. However, you may want to look at using generics as well:
public class MyControllerBase<T> : Controller {
// base class stuff here based on type T's interface
}
public class MyController : MyControllerBase<MyController> {
// regular class here, sending MyController to the base
}
These are pretty much building blocks of OOP. You may do well to get a book that covers the basics of OOP and work through this type of stuff.
There are tow ways :
Use a single Action with different views like return View("View1") or retrun View("View2"), you can make if else conditions there so you will call your function at a single place.
If you want to go with your current procedure(not recommended) then you have to use Action Filter attribute and decorate it on Controller level then every action would execute your logic before execution of your Action

Categories