Let's say I have an async controller like that below ~
public class HomeController : AsyncController
{
public void LoadAync(DumpModel model) {
}
public ActionResult LoadCompleted(string a) {
return View("Index");
}
}
How can I invoke this method from View?
#using (Html.BeginForm("Load", "Home"))
I tried using this one but it doesn't work. I can invoke only when I change "Load" to "LoadAsync" in View. But "LoadCompleted" will never be called.
Am I missing something obvious?
Your spelling of Async in wrong and MVC is convention based.
public void LoadAsync(DumpModel model) {
}
You have a typo. It should be LoadAsync and not LoadAync.
Related
Why can't Web API Core 2 tell these apart?
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values?name=dave
[HttpGet]
public string Get(string name)
{
return $"name is {name}";
}
Here's what happens -
Both http://localhost:65528/api/values and http://localhost:65528/api/values?name=dave cause the first Get() method to execute.
This exact code works fine in Web Api 2.
I know multiple ways of getting around this, but I don't know why it happens.
Can someone explain why this has changed?
I don't think you can even compile your code in ASP.NET Core Mvc 2.0 since you have 2 actions mapped to same route [HttGet] api/values:
AmbiguousActionException: Multiple actions matched.
Remember, ASP.NET Web API uses the HTTP verb as part of the request to figure which action to call. Although it uses conventional routing (you name your actions Get, Post, Put and Delete, etc) if you don't have route attribute specify, I would highly recommend to always use routing attribute to annotate your controllers and actions.
Api Design time
Now it's up to you to design the route, as a developer. Remember the route is supposed to be a Uri that can identify a resource / resources.
Use the name as identifier along with the route
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")] // constraint as a string
public IActionResult GetByName(string name)
{
...
}
}
Use the name as filter, against the resource collection
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
// api/customers?name=dave
[HttpGet]
public IActionResult Get(string name)
{
...
}
}
To confuse you more
api/customers/dave will still execute GetById first!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
[HttpGet("{name}")]
public IActionResult GetByName(string name)
{
...
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
...
}
}
Both methods GetByName and GetById are potential candidates but MVC picks GetById method first because MVC compares the method/template name {name} and {id} through case-insensitive string comparison, and i comes before n.
That's when you want to put constraints.
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
You can also specify the Ordering too!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/portland
[HttpGet("{city:alpha}", Order = 2)]
public IActionResult GetByCity(string city)
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}", Order = 1)]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
Without the Order, the method GetByCity will be in favor than GetByName because character c of {city} comes before the character n of {name}. But if you specify the order, MVC will pick the action based on the Order.
Sigh the post is too long....
Because in your case the best match in the route pipeline is the default httpget attribute (the one that get all). The query is a regular string so if you don't ask what you want from the query the best match is still the one that get all.
[HttpGet]
public string Get([FromQuery]string name)
{
return $"name is {name}";
}
The [FromQuery] is pointing to the key "name" in the query string to get the value.
Your should read Routing in asp.net core
I am playing around with the idea of having a base controller that uses a generic repository to provide the basic CRUD methods for my API controllers so that I don't have to duplicate the same basic code in each new controller. But am running into problems with the routing attribute being recognized when it's in the base controller. To show exactly what the problem I'm having I've created a really simple WebAPI controller.
When I have a Get method in the main Controller and it inherits from the ApiController directly I don't have any problems and this works as expected.
[RoutePrefix("admin/test")]
public class TestController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
When I move the Get method into a base controller it is returning the contents of the 404 page.
[RoutePrefix("admin/test")]
public class TestController : TestBaseController
{
}
public class TestBaseController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
Some more interesting notes:
I can access the action at GET /Test/1. So it is finding it based on the default route still.
When I try to access POST /admin/test, it returns the following JSON
{
"Message":"No HTTP resource was found that matches the request URI 'http://test.com/admin/test'.",
"MessageDetail":"No type was found that matches the controller named 'admin'."
}
Does anyone know of a way to get the routing to work with attributes from a base controller?
Attribute routes cannot be inherited. This was a deliberate design decision. We didn't feel right and didn't see valid scenarios where it would make sense to inherit them.
Could you give a more realistic scenario as to where you would want to use this?
[Update(3/24/2014)]
In the upcoming 5.2 release of MVC Web API, there is going to be an extensibility point called System.Web.Http.Routing.IDirectRouteProvider through which you can enable the inheritance scenario that you are looking for here. You could try this yourself using the latest night builds(documentation on how to use night builds is here)
[Update(7/31/2014)]
Example of how this can be done in Web API 2.2 release:
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
//---------
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
// inherit route attributes decorated on base class controller's actions
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
Using Web API 2.2, you can:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
as outlined here: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22
Got it.
[Route("api/baseuploader/{action}")]
public abstract class BaseUploaderController : ApiController
{
[HttpGet]
public string UploadFile()
{
return "UploadFile";
}
}
[Route("api/values/{action}")]
public class ValuesController : BaseUploaderController
{
[HttpGet]
public string Get(int id)
{
return "value";
}
}
One caveat here is that the route action paramter must be the same as the action name. I could not find a way to get around that. (You cannot rename the route with a RouteAttribute)
I'm following a book called 'Asp.Net MVC4 in Action'. And now at certain point they say, Instead of relying on if statement within our code to check if the request is Ajax or not, we could use an action method selector to differentiate it. And what they have done is create a class AcceptAjaxAttribute with following code
using System;
using System.Reflection;
using System.Web.Mvc;
namespace CustomAjax
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptAjaxAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
return controllerContext.HttpContext.Request.IsAjaxRequest();
}
}
}
and the function in controller which looked like this before
var speaker = _repository.FindSpeaker(id);
if(Request.IsAjaxRequest())
{
return Json(speaker, JsonRequestBehaviour.AllowGet);
}
return View();
has changed to something like this
[AcceptAjax]
public ActionResult Details(int id)
{
var speaker = _repository.FindId(id);
return Json(speaker, JsonRequestBehavior.AllowGet);
}
[ActionName("Details")]
public ActionResult Details_NonAjax(int id)
{
var speaker = _repository.FindId(id);
return View();
}
To be honest I have no idea what is being done or why we created new class and used that[AcceptAjax] thingy. Can someone may be explain it to me.
Before you had one action with an if inside, after the refactoring you have 2 actions each returning a different type of result. The ActionMethodSelectorAttribute is used to select the proper action based on some condition. Since the 2 actions have the same name (Details), ASP.NET MVC will use this attribute to select the one or the other based on whether an AJAX request is being used.
But honestly I don't like this approach at all. You now have 2 actions and have repeated the var speaker = _repository.FindId(id); call twice which is not very DRY. Wouldn't it be better if you had this?
[AcceptAjax]
public ActionResult Details(int id)
{
var speaker = _repository.FindId(id);
return View(speaker);
}
If you are like me and think that this is better, then simply replace this AcceptAjaxAttribute you got from the book with an action filter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptAjaxAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
var result = filterContext.Result as ViewResultBase;
if (result != null && result.Model != null)
{
filterContext.Result = new JsonResult
{
Data = result.Model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
}
The OnActionExecuted method will be invoked after the controller action has finished running and returned some result. Inside this method we verify whether the controller action returned a ViewResultBase (ViewResult or PartialViewResult) and whether a model has been passed. If this is the case we simply replace this result by a JsonResult.
And if you want to avoid decorating all your controller actions with this [AcceptAjax] attribute, you could register it as a global action filter in your ~/App_Start/FilterConfig.cs:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AcceptAjaxAttribute());
}
}
If the authorize attribute has been applied to the controller is it possible to allow unauthorized access to an action/viewresult inside that controller?
Say for example I didn't want authorization to occur on Test2 in the following:
[Authorize]
public class TestController : Controller
{
public ViewResult Test()
{
return View();
}
public ViewResult Test2()
{
return View();
}
}
Thanks in advance.
No, this is not possible. You will have to apply the Authorize attribute on the Test action and not on the controller. Another possibility is to put the Test2 action on another controller which is not decorated with this attribute.
Back in MVC 3 it appears it was indeed not possible to do (as mentioned Darin Dimitrov), but if anyone using MVC 4 (and up) comes across this question, he\she should be able to use AllowAnonymous filter to achieve the result. So the code would become:
[Authorize]
public class TestController : Controller
{
public ViewResult Test()
{
return View();
}
[AllowAnonymous]
public ViewResult Test2()
{
return View();
}
}
I would like to catch the AmbiguousMatchException whenever it is thrown and then write some code to resolve it. For example, I have an action ChangePassword which should be called only if the user is logged in. I have another method RenewPassword which must be called if the user is not logged in. I have given the same action name to both these methods.
[HttpPost]
[ActionName("ChangePassword")]
public ActionResult RenewPassword(ChangePasswordModel model)
{
...
}
[Authorize]
[HttpPost]
[ActionName("ChangePassword")]
public ActionResult ChangePassword(ChangePasswordModel model)
{
...
}
I want to use the same action name because I do not want the view to have to worry about which action to call. I know that I can write a custom FilterAttribute which will do the reverse of what AuthorizeAttribute does, apply it to the RenewPassword method, and thereby resolve the ambiguity. However, this seems like too much work for a very simple need.
Any better ideas? Is there a built in way to say that a particular action should be executed only for anonymous users and not for logged in users?
If you don't views having to worry about which action to call why not writing a reusable HTML helper:
public static class HtmlExtensions
{
public static MvcForm BeginChangePasswordForm(this HtmlHelper htmlHelper)
{
if (htmlHelper.ViewContext.HttpContext.User.Identity.IsAuthenticated)
{
return htmlHelper.BeginForm("ChangePassword", "SomeController");
}
return htmlHelper.BeginForm("RenewPassword", "SomeController");
}
}
and inside your view:
#using (Html.BeginChangePasswordForm())
{
...
}
and in the corresponding controller:
[HttpPost]
public ActionResult RenewPassword(ChangePasswordModel model)
{
...
}
[Authorize]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model)
{
...
}