How exactly does a .net MVC controller know which View() to return? - asp.net

When I want to call a new page in .net, say the "About.cshtml" page, I use the following code in the HomeController:
public ActionResult About()
{
ViewBag.Title = "About";
return View();
}
To call it I'd use a link to "/Home/About". And if I wanted to create a new page called "Contact.cshtml", for example, I'd copy the above and replace About with Contact.
I know that the route in "/Home" calls the HomeController. But how, exactly, does that controller know to return the About.cshtml page? I assume it's based on the name of the function. But this doesn't sound right to me. About() isn't an HTTP verb like Get() or Post(), and the name of the function normally shouldn't define what it does, unless it already existed.
Also, when exactly is View() defined, and when is it assigned to the About.cshtml page?
Finally, is there an attribute that would allow me to return the About.cshtml page with a different function name (as I can set a function to respond to Get with the [HttpGet] attribute)?

But how, exactly, does that controller know to return the About.cshtml page?
Because the action method name is About:
public ActionResult About()
The route found that method by the URL:
/Home/About
If the URL didn't include the action:
/Home
Then it would look for a default action. Normally this is Index(), as configured by the default route mapping:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
Note how a default value is defined for both controller and action if none is provided on the URL.
the name of the function normally shouldn't define what it does
Why on Earth not? A function name should exactly define what that function does.
Also, when exactly is View() defined
It's in the base controller class.
Finally, is there an attribute that would allow me to return the About.cshtml page with a different function name
Not an attribute per se, but you can specify the view name when calling View():
return View("SomeOtherView");

only to explain a few more (the David's response is so good), View() is an object of type ViewResultBase, in class Controller;
protected internal ViewResult View()
{
return View(viewName: null, masterName: null, model: null);
}
ViewResultBase has a method ExecuteResult() that receives a parameter of type ControllerContext (this parameter has the info about the request) and inside this method, if the name of the view is null, the view name is established based on the url (read the explain of David about the routing) that is called accesing to the RouteData:
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (String.IsNullOrEmpty(ViewName))
{
ViewName = context.RouteData.GetRequiredString("action");
}
....
}
Here, if you watch the RouteData you can view that the called action is Index, and this value is set to the ViewName property:
Regards,

Related

asp.net mvc 4 custom urls for action method

I am creating a asp.net mvc 4 application
public class AspNetController : Controller
{
//
// GET: /AspNet/
public ActionResult Index()
{
return View();
}
public ActionResult Introduction()
{
return View();
}
}
as Shown Above There is AspNet Controller and Introduction Action Method
Default Url for Introduction Action Method is
localhost:55134/aspnet/introduction
But I Want Url Like
localhost:55134/aspnet/introduction-To-AspNet
Same for
/localhost:55134/aspnet/NetFrameWork To
/localhost:55134/aspnet/What-is-.Net-Framework
How to do that
You should be able to use the ActionName attribute to decorate your routes.
[ActionName("Introduction-To-AspNet")]
public ActionResult Introduction()
{
return View();
}
You really want to use AttributeRouting, either via a 3rd party package or natively if you can.
Technically this concept comes under Routing in ASP.NET MVC.
For this you need to do an entry for route in App_Start->RouteConfig.cs file under RegisterRoutes(RouteCollection routes)
For Example:
routes.MapRoute(
"customRouteName",
"aspnet/introduction-To-AspNet",
new { controller = "AspNet", action = "Introduction" });
here aspnet/introduction-To-AspNet will append after your base url i.e. localhost:55134/
The quick and dirty answer is to add a route to your ~/AppStart/RouteConfig.cs file and it will be taken care of:
routes.MapRoute(
name: "CustomRoute",
url: "Aspnet/Introduction-To-AspNet",
defaults: new { controller = "Home", action = "AspNet", id = UrlParameter.Optional }
);
However, I'm assuming this is for some type of blog? I would reccomend that you have an action method called view, and then use your name as a parameter for the article. That way, you don't have to go in and edit the code every time you add a new article or other content:
public class ArticlesController : Controller
{
public ActionResult ViewArticle(string? title)
{
ViewBag.Article = title;
return View();
}
}
that way, your URL would be www.yoursite.com/Articles/ViewArticle/Introduction-To-AspNet. In general, you don't want to add tons of specific routes to your route config if you can avoid it. That being said, if this is a legacy system, the route table may be the only way.
EDIT
Ok, so what you can do is pass the string into the ViewBag and use a case statement to determine which partial view to show (I think this just might be your ideal solution):
<!--cshtml view-->
#switch(ViewBag.Article)
{
case 'Introduction-To-AspNet':
#Html.Partial('pathToPartialView.cshtml')
break;
case 'Some-Other-Article'
#Html.Partial('pathToAnotherPartialView.cshtml')
break;
...
...
default:
#Html.Partial('invalidArticleName.cshtml')
break;
}
The controller will pass the article name through the ViewBagand then you can use the case statement to figure out which article to render... and of course, the real secret sauce you've been looking for: #Html.Partial('URL') - this will take your partial and render it right were you put that in the page. You can also pass objects to that just as an FYI.
In addition, make sure that you have a default action on the switch statement that will show some sort of 404 page that indicates that the name in the URL was invalid. You ALWAYS want to have this anytime you're taking user input from the URL because people monkey with URLs all the time (and more innocently, copy+paste them wrong/incompletely all the time)

Fill a form with ASP.NET MVC Querystring

I want to fill a form with Id and i have made the url in this way
local-host:1613/Patient/Index/1
Where as 1 is the Id. And i want to fill my form with the data of patient with Id 1.
But this below function is not being called in Controller
public ActionResult Index(int id)
{
Patient p = new CEntities().Patients.Find(id);
return View(p);
}
instead, i am getting into
public ActionResult Index()
{
return View();
}
I am newbie in ASP.NET MVC, i have no idea in filling a form with any other way, i found a way but it shows my whole object in query string which is insecure. And this above solution is not working.
Please suggest me good solution.
As far as I know you cannot truly overload action methods in ASP.NET-MVC so there can be only one Index action method(without any additional annotations). Of course you can define few methods with the same name but then you might want to add [HttpPost](or other HTTP method) annotation above the action method to use it for instance after submitting a form.
Making parameter optional might be helpful:
public ActionResult Index(int? id){
if(id.HasValue()){
Patient p = new CEntities().Patients.Find(id);
return View(p);
}
return View();
}
Please take a look: Can you overload controller methods in ASP.NET MVC?
Another advice: try to use scaffolding and see how template does it for you:
1.Right click on a controller folder
2.Add->New Controller
3.Controller with Entity Framework read/write actions(as far as I remember 3rd from the top)
4.Select model class.
5.Select db context(class which maintains connection with database).
6.Ok.
It will generate controller with views, have fun.
local-host:1613/Patient/Index/1
This path should be hitting your action method of:
public ActionResult Index(int id)
Please check your RouteConfig.cs file and make sure you have the default route set up correctly:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Try commenting out your other Index action method "//public ActionResult Index()", and see what happens when trying to go to local-host:1613/Patient/Index/1

How do I return a 404 status where invalid parameters are passed to my ASP.NET MVC controller?

I want to return a HTTP status 404 if invalid arguments are passed to my controller. For example if I have a controller that looks like:
public ActionResult GetAccount(int id)
{
...
}
Then I want to return a 404 if say urls such as these are encountered:
/GetAccount
/GetAccount/notanumber
i.e. I want to trap the ArgumentException that is thrown.
I know I could use a nullable type:
public ActionResult GetAccount(int? id)
{
if(id == null) throw new HttpException(404, "Not found");
}
But that's pretty icky and repetitious.
I was hoping I could add this to my controllers where necessary:
[HandleError(View="Error404", ExceptionType = typeof(ArgumentException))]
public class AccountsController : Controller
{
public ActionResult GetAccount(int id)
{
...
}
}
But that doesn't appear to work well.
I saw this post and this answer which nearly solves my problem:
In that answer an abstract BaseController is created from which you derive all your other controllers from:
public abstract class MyController : Controller
{
#region Http404 handling
protected override void HandleUnknownAction(string actionName)
{
// If controller is ErrorController dont 'nest' exceptions
if (this.GetType() != typeof(ErrorController))
this.InvokeHttp404(HttpContext);
}
public ActionResult InvokeHttp404(HttpContextBase httpContext)
{
IController errorController = ObjectFactory.GetInstance<ErrorController>();
var errorRoute = new RouteData();
errorRoute.Values.Add("controller", "Error");
errorRoute.Values.Add("action", "Http404");
errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
errorController.Execute(new RequestContext(
httpContext, errorRoute));
return new EmptyResult();
}
#endregion
}
This works great at handling unknown actions with a 404 but doesn't allow me to handle invalid data as a 404.
Can I safely override Controller.OnException(ExceptionContext filterContext) like this:
protected override void OnException(ExceptionContext filterContext)
{
if(filterContext.Exception.GetType() == typeof(ArgumentException))
{
filterContext.ExceptionHandled = true;
this.InvokeHttp404(filterContext.HttpContext);
}
else
{
base.OnException(filterContext);
}
}
On the surface it seems to work, but am I storing up any problems by doing this?
Is this semantically correct thing to do?
Best way? Action method selector attribute!
To actually avoid nullable method arguments I suggest that you write an Action Method Selector attribute that will actually only match your action method when id is supplied. It won't say that argument wasn't supplied but that it couldn't match any action methods for the given request.
I would call this action selector RequireRouteValuesAttribute and would work this way:
[RequireRouteValues("id")]
public ActionResult GetAccount(int id)
{
...
}
Why is this the best solution for your problem?
If you look at your code you'd like to return a 404 on actions that match name but parameter binding failed (either because it wasn't supplied or any other reason). Your action so to speak requires particular action parameter otherwise a 404 is returned.
So when adding action selector attribute adds the requirement on the action so it has to match name (this is given by MVC) and also require particular action parameters. Whenever id is not supplied this action is not matched. If there's another action that does match is not the issue here because that particular action will get executed. The main thing is accomplished. Action doesn't match for invalid route request and a 404 is returned instead.
There's an app code for that!
Check my blog post that implements this kind of attribute that you can use out of the box. It does exactly what you're after: it won't match your action method if route data provided doesn't have all required values.
Disclaimer: this does not cover all the cases
For urls in your examples, returning 404 can be done in single line. Just add route constraint for id parameter.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index" }, // Parameter defaults
new { id = #"\d+" } // restrict id to be required and numeric
);
And that's all. Now any matching url that has no id or id is not numeric, autimatically triggers not found error (for which there are plenty of ways to handle, one in your example, another by using custom HandleErrorAttribute, etc). And you can use non-nullable int parameters on your actions.
I managed to get this working by adding this route at the end of all routes:
routes.MapRoute("CatchAllErrors", "{*url}",
new { controller = "Error", action = "NotFound" }
);
Note: First I followed this: How can I properly handle 404 in ASP.NET MVC?

Custom route with more than one parameter in url (customer, extra info)

I have a multi tenant system and I need to allow administrators to edit the information of other customers.
I believe the correct way to do this is to append the customer ID to each request, and update my routes accordingly.
How do I make ASP.NET accept and use something like this in the controller (order doesn't mean much to me):
mysite.com/files/delete/{file}/{customerID}
and how would I pass and consume that in my controller? The ASP.NET tutorials skip this... (or I don't know where to search)
Define a route:
//mysite.com/files/delete/{file}/{customerID}
routes.MapRoute(
"Files", // Route name
"files/delete/{file}/{customerID}", // URL with parameters
new { controller = "File", action = "Delete" } // Parameter defaults
);
Create a Controller:
public class FileController : Controller
{
public ActionResult Delete(string file, string customerID)
{
//do something
return View();
}
}
I presume that {file} is a filename that should be deleted.

ASP.NET MVC route returning 404 without action

I am working on a very simple application, using MVC2 Preview 1.
I have a controller named ContentController. My problem is that /Content/Index works correctly, but /Content/ returns a 404. I am running the application on the Studio Development Server.
Tested with RouteDebugger but /Content/ returns a 404, and does not display any debugging information.
I have not changed the routing code:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
This is my controller:
public class ContentController : Controller
{
IRepository _repo = new SimpleRepository("db", SimpleRepositoryOptions.RunMigrations);
public ActionResult Index()
{
var content = _repo.GetPaged<Content>(0, 20);
return View(content);
}
It's a shot in the dark, but do you have a directory named /Content/ as well?
/Content is a controller, which is basically just a collection of actions. ASP.NET MVC needs to know WHICH action you want to run, so by leaving out the action asp.net mvc doesn't know what action to return and gives a 404.
You can tell it a default either by adding a route:
eg:
routes.MapRoute("ContentDefault", "Content", new {controller = "Content", action = "Index"});
The attributes are defined as follows:
'ContentDefault`: Name of the Route (must be unique in your routing table)
Content: The URL segment (try changing this to 'Content/Much/Longer/URL' and then go to http://localhost/Content/Much/Longer/URL to see how this works)
new {controller=.., action=...}: which controller/action combo to run for this route.
You could also override HandleUnknownAction in your controller:
protected override void HandleUnknownAction(string actionName)
{
return RedirectToAction("index");
}
Oh and incidentally, an extra piece of advice about routing.... if you add something to the route in braces { } these will be passed to the action as an attribute.
e.g. /Content/Much/Longer/Url/{page}
so the URL http://localhost/Content/Much/Longer/Url/999
will pass the 999 into your action, as the page attribute
public ActionResult Index(int Page) { }
I love MVC - never going back to WebForms - this is how web development should be!

Resources