What's an equivalent of ASP.NET MVC 5
Controller.HandleUnknownAction()
in ASP.NET MVC 6 / ASP.NET 5?
There's no real equivalent.
Action Selection in MVC5/WebAPI2 was a three stage process:
1. Run the routes
2. Select a controller
3. Select an action
In MVC6, step 2 is gone. Actions are selected directly using route values - you'll notice that Controller.BeginExecute is gone as well. Controllers are 'thin' now.
You can simulate this behavior if you want by using a route that goes directly to your action in question.
Define an action called HandleUnknownAction in your controller
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute("unknown_action", "{controller}/{*params}", defaults: new { action = "HandleUnknownAction"});
An alternative approach is to simply define the unknown action as a parameter of your route:
[Route("[controller]")]
public class FooController : Controller
{
[HttpGet("{viewName}")]
public IActionResult HandleUnknownAction(string viewName)
{
return View(viewName);
}
}
Using this approach, the url foo/bar would return the View bar.cshtml, foo/baz would return baz.cshtml etc.
Related
I have an action in my ASP.Net Core WebAPI Controller which takes one parameter. I'm trying to configure it to be able to call it in following forms:
api/{controller}/{action}/{id}
api/{controller}/{action}?id={id}
I can't seem to get the routing right, as I can only make one form to be recognized. The (simplified) action signature looks like this: public ActionResult<string> Get(Guid id). These are the routes I've tried:
[HttpGet("Get")] -- mapped to api/MyController/Get?id=...
[HttpGet("Get/{id}")] -- mapped to api/MyController/Get/...
both of them -- mapped to api/MyController/Get/...
How can I configure my action to be called using both URL forms?
if you want to use route templates
you can provide one in Startup.cs Configure Method Like This:
app.UseMvc(o =>
{
o.MapRoute("main", "{controller}/{action}/{id?}");
});
now you can use both of request addresses.
If you want to use the attribute routing you can use the same way:
[HttpGet("Get/{id?}")]
public async ValueTask<IActionResult> Get(
Guid id)
{
return Ok(id);
}
Make the parameter optional
[Route("api/MyController")]
public class MyController: Controller {
//GET api/MyController/Get
//GET api/MyController/Get/{285A477F-22A7-4691-AA51-08247FB93F7E}
//GET api/MyController/Get?id={285A477F-22A7-4691-AA51-08247FB93F7E}
[HttpGet("Get/{id:guid?}"
public ActionResult<string> Get(Guid? id) {
if(id == null)
return BadRequest();
//...
}
}
This however means that you would need to do some validation of the parameter in the action to account for the fact that it can be passed in as null because of the action being able to accept api/MyController/Get on its own.
Reference Routing to controller actions in ASP.NET Core
I'm starting a new ASP.NET project after a few years developing in MVC4, and I have a question regarding architecture.
At the top corner of each page, I will display details of the current logged in user.
In MVC4 I achieved something like this by creating a BaseController, which created an EF data connection, and set up some common variables that would be used on every page - CurrentUser being one of them.
Now that I'm using Core, this approach doesn't seem to work, and certainly isnt mockable.
What would be the correct way to achieve something like this via ASP.NET Core?
I need the same variables on every view, and certainly dont want to have to write the code in each controller action!
You can use View Components feature in asp.net core to implement that functionality.
//In your ConfigureServices method , add your services that will be injected whenever view component is instantiated
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IUserRespository, UserRepository>();
}
//Now Create a view component
public class LoggedInUser : ViewComponent
{
private IUserRespository userRepository;
//Services can be injected using asp.net core DI container
public LoggedInUser(IUserRepository userRepository,SomeOtherService service)
{
//assign services to local variable for use later
this.userRepository = userRepository;
}
//This method can take any number of parameters and returns view
public async Task<IViewComponentResult> InvokeAsync(int param1,string param2,etc )
{
//get the logged in user data here using available services
var loggedInUserData = GetSomeData(context);
return View(loggedInUserData );
}
}
Create view file # View/Shared/Components/LoggedInUser/Default.cshtml.View can be strongly typed.
#model LoggedInUserModel
<div>
<!-- html here to render model -->
</div>
Now, since you use to display this data on every page , you need to apply _Layout.chstml to all your pages . In the _Layout.chstml , you can render view component defined above with any additional parameter you would like to pass as anonymous type.
#await Component.InvokeAsync("LoggedInUser", new { param1=value,param2=value,etc })
Testing the View Component:
var mockRepository = Mock of ICityRepository;
var viewComponent= new LoggedInUser(mockRepository);
ViewViewComponentResult result
= viewComponent.Invoke() as ViewViewComponentResult; //using Invoke here instead of InvokeAsnyc for simplicity
//Add your assertions now on result
Note :
It is also possible to decorate a controller with [ViewComponent(Name = "ComponentName")] attribute and define public IViewComponentResult Invoke()
or public IViewComponentResult InvokeAsync() to turn them in to hybrid controller - view component.
I'm having trouble with an ActionLink in MVC 5.
#Html.ActionLink("View Commissions", "/" + item.Id.ToString, "Commissions")
#Html.ActionLink("View Commissions", "Index", "Commissions", New With {Key .payRollId = item.Id}, Nothing)
These two ActionLinks should accomplish the same thing, but I would prefer to use the second one. Unfortunately, they produce different URLs. The first creates http://mysite/Commissions/3. The second creates http://mysite/Commissions?payRollId=3.
In my Commissions controller, I have the following code:
' GET: Commissions/5
<Route("Commissions/{payRollId:min(1)}")>
Async Function Index(ByVal payRollId As Integer?) As Task(Of ActionResult)
If IsNothing(payRollId) Then
Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
End If
Return View(Await ...query...).ToListAsync)
End Function
This successfully handles the first ActionLink's URL. The second one results in a 404 error. I don't have any other RouteAttributes or mapped routes for Commissions. According to this attribute routing article, the second ActionLink should create the pretty URL (no query string) that successfully handles the request.
What am I missing? How can I get the second ActionLink to generate the proper URL (Commissions/3) to match the RouteAttribute?
Edit
This should produced the desired route:
View Commissions
This assumes you've enabled attribute based routing something like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
}
I have a partial solution. I played with the controller code and found that changing the RouteAttribute to <Route("Commissions/{payRollId:min(1)?}")> (note the ? at the end) allows it to handle the second URL.
I am still working on how to generate a pretty URL using the second ActionLink. I will update this answer if I work it out.
I have a controller named DummyController when i call the controller it is like DummyController/Index i want this controller to be called as maincontroller/dummycontroller/index where mainController is a different controller altogether.
Code for DummyController:
public ActionResult Index()
{
return View("~/Views/main/dummy/index.cshtml",db.Users.ToList());
}
the location of index file of Dummy Controller is main/dummy
Now the problem is when I call the dummy controller's index page i get the url as dummy/index i want the url to be displayed as main/dummy/index.
Is there any way to create child controllers? o change the url only for the specific controller
This was relatively straightforward, once I got past a simple issue.
By using a combination of [RoutePrefix("")] & [Route("")] on my controller, I was able to make this work. Attribute Routing requires a call to routes.MapMvcAttributeRoutes() in your Global.asax.cs RegisterRoutes() method.
[RoutePrefix("main/dummy")]
[Route("{action=index}/{id:long?}")]
{action=index} defines the action handling for the route, and specifies /index as the default action, if it's not supplied (ie, ~/main/dummy/ will render ~/main/dummy/index
{id:long?} specifies the id route attribute
:long is the syntax for constraining the param to the long datatype
? denotes this param is optional. (more on this here)
This could also be done using the standard MapRoute() technique:
routes.MapRoute(
name: "DummyControllerRoute",
url: "main/dummy/{action}/{id}",
defaults: new { controller = "Dummy", action = "Index", id = UrlParameter.Optional });
I forgot to add what my simple issue was..
I had 2 actions in the controller: Index() and Index(id), which would always result in the AmbiguousRouteException, and was leading me to believe my Route was defined incorrectly. Changing that to View(id) solved it. (I guess technically the route was wrong, but I didn't need to spend any more time trying to make it work that way)
Is it possible to get/render View without creating Action in Controller? I have many Views where I dont need pass any model or viewbag variables and I thing, its useless to create only empty Actions with names of my Views.
You could create a custom route, and handle it in a generic controller:
In your RouteConfig.cs:
routes.MapRoute(
"GenericRoute", // Route name
"Generic/{viewName}", // URL with parameters
new { controller = "Generic", action = "RenderView", }
);
And then implement a controller like this
public GenericContoller : ...
{
public ActionResult RenderView(string viewName)
{
// depending on where you store your routes perhaps you need
// to use the controller name to choose the rigth view
return View(viewName);
}
}
Then when a url like this is requested:
http://..../Generic/ViewName
The View, with the provided name will be rendered.
Of course, you can make variations of this idea to adapt it to your case. For example:
routes.MapRoute(
"GenericRoute", // Route name
"{controller}/{viewName}", // URL with parameters
new { action = "RenderView", }
);
In this case, all your controllers need to implement a RenderView, and the url is http://.../ControllerName/ViewName.
In my opinion it's not possible, the least you can create is
public ActionResult yourView()
{
return View();
}
If these views are partial views that are part of a view that corresponds to an action, you can use #Html.Partial in your main view to render the partial without an action.
For example:
#Html.Partial("MyPartialName")