Why is my attribute routing not working? - asp.net-mvc

This is what my controller looks like:
[Route("api/[controller]")]
[Produces("application/json")]
public class ClientsController : Controller
{
private readonly IDataService _clients;
public ClientsController(IDataService dataService)
{
_clients = dataService;
}
[HttpPost]
public int Post([Bind("GivenName,FamilyName,GenderId,DateOfBirth,Id")] Client model)
{
// NB Implement.
return 0;
}
[HttpGet("api/Client/Get")]
[Produces(typeof(IEnumerable<Client>))]
public async Task<IActionResult> Get()
{
var clients = await _clients.ReadAsync();
return Ok(clients);
}
[HttpGet("api/Client/Get/{id:int}")]
[Produces(typeof(Client))]
public async Task<IActionResult> Get(int id)
{
var client = await _clients.ReadAsync(id);
if (client == null)
{
return NotFound();
}
return Ok(client);
}
[HttpGet("api/Client/Put")]
public void Put(int id, [FromBody]string value)
{
}
[HttpGet("api/Client/Delete/{id:int}")]
public void Delete(int id)
{
}
}
Yet when I request the URL /api/Clients/Get, as follows:
json = await Client.GetStringAsync("api/Clients/Get");
I get back the following exception:
AmbiguousActionException: Multiple actions matched. The following
actions matched route data and had all constraints satisfied:
Assessment.Web.Controllers.ClientsController.Index (Assessment.Web)
Assessment.Web.Controllers.ClientsController.Details (Assessment.Web)
Assessment.Web.Controllers.ClientsController.Create (Assessment.Web)
Client is an HttpClient. Please note that no GET action matched the route data, despite the same name, id.
What could be wrong here?

You are using the attributes wrong.
You have a route on the controller
[Route("api/[controller]")]
which would map to api/Clients and prefix that to any action routes in the controller.
So that means that
[HttpGet("api/Client/Get")] // Matches GET api/Clients/api/Client/Get
[Produces(typeof(IEnumerable<Client>))]
public async Task<IActionResult> Get()
{
var clients = await _clients.ReadAsync();
return Ok(clients);
}
Matches a GET to api/Clients/api/Client/Get because of the route prefix on the controller.
Referencing Routing to Controller Actions
You need to update the attribute routes on the actions accordingly
[Route("api/[controller]")]
[Produces("application/json")]
public class ClientsController : Controller {
private readonly IDataService _clients;
public ClientsController(IDataService dataService)
{
_clients = dataService;
}
[HttpPost] //Matches POST api/Clients
public int Post([Bind("GivenName,FamilyName,GenderId,DateOfBirth,Id")] Client model) {
// NB Implement.
return 0;
}
[HttpGet("Get")] //Matches GET api/Clients/Get
[Produces(typeof(IEnumerable<Client>))]
public async Task<IActionResult> Get() {
//...code removed for brevity
}
[HttpGet("Get/{id:int}")] //Matches GET api/Clients/Get/5
[Produces(typeof(Client))]
public async Task<IActionResult> Get(int id) {
//...code removed for brevity
}
[HttpGet("Put")] //Matches PUT api/Clients/Put
public void Put(int id, [FromBody]string value) {
//...code removed for brevity
}
[HttpGet("Delete/{id:int}")] //Matches GET api/Clients/Delete/5
public void Delete(int id) {
}
}
The delete action should actually be refactored to a HTTP DELETE and should return a IActionResult
[HttpDelete("Delete/{id:int}")] //Matches DELETE api/Clients/Delete/5
public IActionResult Delete(int id) {
//...code removed for brevity
}

I Think there is a typo in the route, it should be Clients with an (s) at the end instead of Client.
[HttpGet("api/Clients/Get")]
instead of
[HttpGet("api/Client/Get")]
Or change the call to the endpoint to :
json = await Client.GetStringAsync("api/Client/Get");

Related

How to get different response type for the same request model using MediatR?

I am trying to understand how MediatR works. Never used this library before. Below code is not actual production code. It is only for understanding purpose.
Lets say I have two RequestHandlers. Each handler takes ProductModel as request but returns different type of response.
public class GetOrdersHandler : IRequestHandler<ProductModel, IEnumerable<Order>>
{
private readonly FakeDataStore _fakeDataStore;
public GetOrdersHandler(FakeDataStore fakeDataStore)
{
_fakeDataStore = fakeDataStore;
}
public async Task<IEnumerable<Order>> Handle(ProductModel request,CancellationToken cancellationToken
{
return await _fakeDataStore.GetAllOrdersForProduct(request);
}
}
public class SaveProductHandler : IRequestHandler<ProductModel, Product>
{
private readonly FakeDataStore _fakeDataStore;
public SaveProductHandler(FakeDataStore fakeDataStore)
{
_fakeDataStore = fakeDataStore;
}
public async Task<Product> Handle(ProductModel request,CancellationToken cancellationToken)
{
return await _fakeDataStore.SaveProduct(request);
}
}
Then in the same controller I have two action methods
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<ActionResult> GetAllOrders(ProductModel model)
{
var product = await _mediator.Send(model);
return Ok(product);
}
[HttpPost]
public async Task<ActionResult> SaveProduct(ProductModel model)
{
var product = await _mediator.Send(model);
return Ok(product);
}
}
Based on the MediatR code this may not work. Looks like the Send method creates instance of handler based on Request type. It keeps dictionary of RequestType and corresponding handler.
If my assumption is correct then does that mean I will have to create unique request model for each action method that will be using Send method?

How to include Query String in the route resolution in order to allow multiple Actions with the same Method, Route and Query String?

I am converting an ASP.NET MVC (.NET Framework) application to ASP.NET Core MVC. This is strictly a conversion, I cannot make any breaking changes hence I cannot change any Routes or Methods. I am unable to match the same functionality in ASP.NET Core MVC.
Working ASP.NET MVC:
[HttpPut]
[Route("status")]
public async Task<IHttpActionResult> UpdateStatusByOrderGuid([FromUri] Guid orderGUID, [FromBody] POST_Status linkStatusModel)
{
}
[HttpPut]
[Route("status")]
public async Task<IHttpActionResult> UpdateStatusById([FromUri] Guid id, [FromBody] POST_Status linkStatusModel)
{
}
Not working, ASP.NET Core MVC.
I get an error:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints
Code:
[HttpPut]
[Route("status")]
public async Task<IActionResult> UpdateStatusByOrderGuid([FromQuery] Guid orderGUID, [FromBody] POST_Status statusModel)
{
}
[HttpPut]
[Route("status")]
public async Task<IActionResult> UpdateStatusById([FromQuery] Guid id, [FromBody] POST_Status statusModel)
{
}
I need to include the query parameters when it resolves which route. It should match based on whether orderGUID or id is in the query string.
Thanks.
You needs to custom ActionMethodSelectorAttribute:
1.QueryStringConstraintAttribute:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class QueryStringConstraintAttribute : ActionMethodSelectorAttribute
{
public string ValueName { get; private set; }
public bool ValuePresent { get; private set; }
public QueryStringConstraintAttribute(string valueName, bool valuePresent)
{
this.ValueName = valueName;
this.ValuePresent = valuePresent;
}
public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{
var value = routeContext.HttpContext.Request.Query[this.ValueName];
if (this.ValuePresent)
{
return !StringValues.IsNullOrEmpty(value);
}
return StringValues.IsNullOrEmpty(value);
}
}
2.Controller:
[HttpPut]
[Route("status")]
[QueryStringConstraint("orderGUID",true)]
[QueryStringConstraint("id", false)]
public void UpdateStatusByOrderGuid([FromQuery] Guid orderGUID,[FromBody]POST_Status model)
{
}
[HttpPut]
[Route("status")]
[QueryStringConstraint("id", true)]
[QueryStringConstraint("orderGUID", false)]
public void UpdateStatusById([FromQuery] Guid id, [FromBody]POST_Status model)
{
}
3.Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Result:
Why not use a single endpoint instead? You don't need to pass Guid's, since it's a GET operation, you can pass strings and cast them later. That way you can send one parameter or the other.
[HttpPut]
[Route("status")]
public async Task<IActionResult> UpdateStatus([FromBody] POST_Status statusModel, [FromQuery] string orderGUID = null, [FromQuery] string id = null)
{
if (!string.IsNullOrEmpty(orderGUID))
{
// UpdateStatusByOrderGuid implementation here
// Guid guid = Guid.Parse(orderGUID);
}
else if (!string.IsNullOrEmpty(id))
{
// UpdateStatusById implementation here
// Guid guid = Guid.Parse(id);
}
else
{
throw new ArgumentException("No valid GUID.");
}
}
This endpoint should be compatible with both scenarios you specified.

The resource cannot be found Error

This error comes to me.
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: /Dinner
I searched but there is not suitable answers for me.
The spell of my controller is correct. I tried and checked many times.
I did not customize my rounting Globle.asax
I checked the "Web" tab under my project's properties, the "SpecificPage is tickled without any contents"
Everything is by default. Anyway here is the default code for rounting. Who knows why?Thanks
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Here is my Dinner controller
namespace NerdDinner.Controllers
{
public class DinnerController : Controller
{
IDinnerRepository _repository;
public DinnerController()
{
_repository = new sqlDinnerRepository();
}
public DinnerController(IDinnerRepository repository)
{
_repository = repository;
}
//
// GET: /Dinner/
public ActionResult Index()
{
var dinners = _repository.FindAllDinners();
return View(dinners);
}
//
// GET: /Dinner/Details/5
public ActionResult Details(int id)
{
var dinner = _repository.GetDinner(id);
return View(dinner);
}
//
// GET: /Dinner/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Dinner/Create
[HttpPost]
public ActionResult Create(Dinner dinner)
{
try
{
// TODO: Add insert logic here
_repository.AddDinner(dinner);
_repository.UpdateDinner(dinner);
return RedirectToAction("Index");
}
catch
{
return View(dinner);
}
}
//
// GET: /Dinner/Edit/5
public ActionResult Edit(int id)
{
var dinner = _repository.GetDinner(id);
return View(dinner);
}
//
// POST: /Dinner/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var dinner = _repository.GetDinner(id);
try
{
// TODO: Add update logic here
UpdateModel(dinner, collection.ToValueProvider());
_repository.UpdateDinner(dinner);
return RedirectToAction("Index");
}
catch
{
return View(dinner);
}
}
//
// POST: /Dinner/Delete/5
[HttpPost]
public ActionResult Delete(int id)
{
var db = new dbDataContext();
var dinner = db.Dinners.SingleOrDefault(x => x.DinnerID == id);
try
{
// TODO: Add delete logic here
_repository.DeleteDinner(dinner);
_repository.UpdateDinner(dinner);
return RedirectToAction("Index");
}
catch
{
return View(dinner);
}
}
}
}
Here is IDinnerRepository interface
namespace NerdDinner.Models
{
interface IDinnerRepository
{
IQueryable<Dinner> FindAllDinners();
Dinner GetDinner(int id);
void AddDinner(Dinner dinner);
void UpdateDinner(Dinner dinner);
void DeleteDinner(Dinner dinner);
}
}
sqlDinnerRepository class implements IDinnerRepository interface
namespace NerdDinner.Models
{
public class sqlDinnerRepository
{
dbDataContext db;
public sqlDinnerRepository()
{
db = new dbDataContext();
}
public IQueryable<Dinner> FindAllDinners()
{
return db.Dinners;
}
public Dinner GetDinner(int id)
{
return db.Dinners.SingleOrDefault(x => x.DinnerID == id);
}
public void AddDinner(Dinner dinner)
{
db.Dinners.InsertOnSubmit(dinner);
}
public void UpdateDinner(Dinner dinner)
{
db.SubmitChanges();
}
public void DeleteDinner(Dinner dinner)
{
db.Dinners.DeleteOnSubmit(dinner);
}
}
}
I type "http://localhost:52372/Dinner" in my browser.

Is it possible to have both GET and POST asynchronous controller actions of the same name?

Is it possible to have an AsyncController that has a GET and POST action of the same name?
public class HomeController : AsyncController
{
[HttpGet]
public void IndexAsync()
{
// ...
}
[HttpGet]
public ActionResult IndexCompleted()
{
return View();
}
[HttpPost]
public void IndexAsync(int id)
{
// ...
}
[HttpPost]
public ActionResult IndexCompleted(int id)
{
return View();
}
}
When I tried this I got an error:
Lookup for method 'IndexCompleted' on controller type 'HomeController' failed because of an ambiguity between the following methods:
System.Web.Mvc.ActionResult IndexCompleted() on type Web.Controllers.HomeController
System.Web.Mvc.ActionResult IndexCompleted(System.Int32) on type Web.Controllers.HomeController
Is it possible to have them co-exist in any way or does every asynchronous action method have to be unique?
You can have the multiple IndexAsync methods, but only the one IndexCompleted method eg:
public class HomeController : AsyncController
{
[HttpGet]
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment(1);
// ...
AsyncManager.Parameters["id"] = null;
AsyncManager.OutstandingOperations.Decrement();
// ...
}
[HttpPost]
public void IndexAsync(int id)
{
AsyncManager.OutstandingOperations.Increment(1);
// ...
AsyncManager.Parameters["id"] = id;
AsyncManager.OutstandingOperations.Decrement();
// ...
}
public ActionResult IndexCompleted(int? id)
{
return View();
}
}
(Only the attributes on the MethodNameAsync methods are used by MVC, so are not required on the MethodNameCompleted methods)

How can I inherit an ASP.NET MVC controller and change only the view?

I have a controller that's inheriting from a base controller, and I'm wondering how I can utilize all of the logic from the base controller, but return a different view than the base controller uses.
The base controller populates a model object and passes that model object to its view, but I'm not sure how I can access that model object in the child controller so that I can pass it to the child controller's view.
A couple points. You can type your return value as ViewResult if you know that's all you're going to return. Then you can interrogate that value from the overridden implementation. More importantly, according to the MVC v1 source, calling View(object) simply sets the ViewData.Model on the controller, then constructs a ViewResult.
Controller.cs:440
protected internal ViewResult View(object model) {
return View(null /* viewName */, null /* masterName */, model);
}
Controller.cs:456
protected internal virtual ViewResult View(string viewName, string masterName, object model) {
if (model != null) {
ViewData.Model = model;
}
return new ViewResult {
ViewName = viewName,
MasterName = masterName,
ViewData = ViewData,
TempData = TempData
};
}
So all you need to do is call the base method and call View(string).
namespace BaseControllers
{
public class CoolController
{
public virtual ViewResult Get()
{
var awesomeModel = new object();
return View(awesomeModel);
}
}
}
public class CoolController : BaseControllers.CoolController
{
public override ViewResult Get()
{
var ignoredResult = base.Get();
// ViewData.Model now refers to awesomeModel
return View("NotGet");
}
}
Of course you waste CPU cycles constructing the ViewResult that you ignore. So instead you can do this:
public class CoolController : BaseControllers.CoolController
{
public override ViewResult Get()
{
var baseResult = base.Get();
baseResult.ViewName = "NotGet";
return baseResult;
}
}
If your base controller returns ActionResult, you'll have to cast it to ViewResult before changing the ViewName.
Sample from my app:
Base class:
public abstract class BaseTableController<T,TU> : BaseController where TU : IGenericService<T>,IModelWrapperService
{
protected readonly TU _service;
public BaseTableController(TU service)
{
_service = service;
_service.ModelWrapper = new ControllerModelStateWrapper(ModelState);
}
public ActionResult Index()
{
return View(_service.List());
}
Inherited:
public class SeverityController : BaseTableController<Severity, ISeverityService>
{
public SeverityController(ISeverityService service)
: base(service)
{
}
//NO CODE INSIDE
}
SeverityController.Index() leads to Views/Severity/Index.aspx. Just had to prepare view. Severity is one of dictionared in my bug tracking application. Every dictionary has similar logic, so I could share some code.
Based on the feedback given on this thread, I've implemented a solution like the one proposed by Antony Koch.
Instead of using an abstract method, I used a concrete, virtual GetIndex method so that I could put logic in it for the base controller.
public class SalesController : Controller
{
// Index view method and model
public virtual ActionResult GetIndex()
{
return View("Index", IndexModel);
}
protected TestModel IndexModel { get; set; }
public virtual ActionResult Index()
{
ViewData["test"] = "Set in base.";
IndexModel = new TestModel();
IndexModel.Text = "123";
return GetIndex();
}
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult Index(TestModel data, FormCollection form)
{
TryUpdateModel(data, form.ToValueProvider());
IndexModel = data;
return GetIndex();
}
}
// This class will need to be in a different namespace or named differently than the
// parent controller
public class SalesController : MyApp.Controllers.BaseControllers.SalesController
{
// Index view method and model
public override ActionResult GetIndex()
{
return View("ClientIndex", IndexModel);
}
public override ActionResult Index()
{
return base.Index();
}
[AcceptVerbs(HttpVerbs.Post)]
public override ActionResult Index(TestModel data, FormCollection form)
{
return base.Index(data, form);
}
}
public class BaseController : Controller {
protected BaseController() {}
public ActionResult Index()
{
return GetIndex();
}
public abstract ActionResult GetIndex(); }
public class MyController : BaseController {
public MyController() {}
public override GetIndex()
{
return RedirectToAction("Cakes","Pies");
}
}
Just use abstraction to call the bits you need from the sub-classes.
I ended up just putting an extra parameter on the base Controller -- viewName.
Seems to work just fine.
Am I missing any major downsides?
public class SalesController : Controller
{
public virtual ActionResult Index(string viewName)
{
ViewData["test"] = "Set in base.";
TestModel model = new TestModel();
model.Text = "123";
return String.IsNullOrEmpty(viewName) ? View(model) : View(viewName, model);
}
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult Index(TestModel data, FormCollection form, string viewName)
{
TryUpdateModel(data, form.ToValueProvider());
return String.IsNullOrEmpty(viewName) ? View(data) : View(viewName, data);
}
}
public class SalesController : MyApp.Controllers.BaseControllers.SalesController
{
public override ActionResult Index(string viewName)
{
return base.Index("ClientIndex");
}
[AcceptVerbs(HttpVerbs.Post)]
public override ActionResult Index(TestModel data, FormCollection form, string viewName)
{
return base.Index(data, form, "ClientIndex");
}
}

Resources