Basic MVC routing query using default project template - asp.net-mvc-3

I am in the process of learning MVC 3 using the basic project template coupled with several examples I have. Things are going well, but now I am trying to implement my controllers and I am having a couple of issues.
So far I have modified the _Layout.cshtml file to have a new link with a specified route defined:
<header>
<div id="title">
<h1>My MVC Application</h1>
</div>
<div id="logindisplay">
#Html.Partial("_LogOnPartial")
</div>
<nav>
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.RouteLink("Contracts", "Contract")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
</ul>
</nav>
</header>
and my global.asax.cs file is as follows:
routes.MapRoute(
"Contract",
"Contract",
new { controller = "Contract", action = "List", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
This works fine as in it returns the expected action view from my Contract controller.
However I would like to modify this to accept an id into the List action. I know that I need to change the List method to accept a parameter, no problem there, but the issue it with the route and how to pass this paramter into the List method from the RouteLink in the _Layout.cshtml file. I have tried a few things, but this bit is really stumping me.
I intend to pass an id from the User that I logged in as through the AccountController, however I will ask another question about that to keep this more consise.
Thank you very much.

You don't actually need your Contract route, as your Default route will work for any controller and action that corresponds to the pattern controller/action/(optional id parameter here). See the comment in the template actually says Parameter defaults. This means, if there is no Controller, Action, or id passed in, it will default to those values. That's why you can just browse to the root of the website and the Home controller's Index action is the default call.
When using routes, you need to remember that the route parameter names need to match the parameter names in your actions.. for example, your Default route currently lets you do this:
[HttpGet]
public ActionResult MyAction(int id) {
}
But, if you changed your default route to be this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{myIDParameter}", // URL with parameters
new { controller = "Home", action = "Index", myIDParameter = UrlParameter.Optional } // Parameter defaults
);
..your Index action would no longer bind the integer parameter properly.. you would have to change the action to this:
[HttpGet]
public ActionResult MyAction(int myIDParameter) {
}
In answer to your question, it might make more sense to use an ActionLink, like the other two you already have:
#Html.ActionLink("Contracts", "Contract", "ActionMethodHere", new { id = UserIdHere }, null)
That assumes though, that you remove your Contract route and just use the default route.

Related

Why is my textBoxFor using my route data?

So I have these three lines:
<div style="background-color: lightgreen;">#Html.TextBoxFor(m => m.Id)</div>
<div style="background-color: green;">#Html.DisplayTextFor(m => m.Id)</div>
<div style="background-color: pink;">#Model.Id</div>
I've identified that the lightgreen value is not my Model.Id but the Id that is set by my route:
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "MyFunController", action = "Index", id = UrlParameter.Optional });
I've come accross some explanations here:
http://forums.asp.net/t/1792086.aspx/1
http://www.hanselman.com/blog/TheWeeklySourceCode38ASPNETMVCBetaObscurityModelStateIsValidIsFalseBecauseModelBinderPullsValuesFromRouteData.aspx
http://ayende.com/blog/3683/reproducing-a-bug
But they have all left me on my appetite. I'm looking for a smart way to work around this, I don't want to change my model's property names nor do I want to change the name of the route item. If I do it will represent a lot of work for me and is not ideal.
I'm sure I'm not the only one with this issue?
(This is MVC 4)
Thanks!
You could remove the problematic value from the ModelState (which is where the Html helpers are taking it from) in the controller action that is rendering the view:
public ActionResult SomeAction(int id)
{
ModelState.Remove("Id");
MyViewModel model = ...
return View(model);
}
Now it's the Id property of your view model that's gonna get used by the TextBox and not the one coming from the route.
Obviously that's only an ugly horrible workaround. The correct way is to of course properly define your view models so that you do not have such naming collisions.

ASP.NET MVC 3 – How can I route directly to an action

I have a controller called HomeController with the actions Index(), MethodOne() and MethodTwo(). Each of these just return View() for now.
When I create an action link with the code:
#Html.ActionLink("Home", "Index", "Home")
I get the hyperlink:
Home
This is good however using the same ActionLink extention method to route too MethodOne I get the hyperlink:
MethodOne</li>
What do I need to configure so my site will accept the hyperlink “/MethodOne”, this currently returns a 404.
I have a suspicion this may be related to routing. My routing table is currently the default:
routes.MapRoute("Default", "{controller}/{action}/{id}",
new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
});
You need a route like:
routes.MapRoute(
"MethodOne", //Your route name.
"methodone", //What your browser/user will see at the URL bar: localhost.com/methodone
new { controller = "Home", action = "MethodOne" } //What MVC3 will route to.
);
That will map and catch the request to the Home/MethodOne action method.

ASP.NET MVC 3 Controller route - make everything under home controller appear under the domain

Currently everything under homecontroller appears in the URL as
example.com/Home/{Action}
Is there a way we can keep all other routing the way it is but ONLY special case home controller so everything under home comes under the domain.
like
example.com/about
example.com/contact
example.com/error
instead of creating new controller classes for each of them.
EDIT:
The other URL's like
example.com/user/details/123
example.com/user/edit/123
Which are in the userController should work the same as they are now
I think the best way is:
routes.MapRoute("home", "home", new { controller = "Home", action = "Index" });
routes.MapRoute("about", "about", new { controller = "Home", action = "About" });
routes.MapRoute("contact", "contact", new { controller = "Home", action = "Contact" });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
and when you want to create a link, use:
#Html.RouteLink("Home", "home", new{/* route values */}, new {/* html attribues */})
OR:
#Html.RouteLink("Home", "home")
instead of:
#Html.ActionLink("Home", "Index", "Home", new{/* route values */}, new {/* html attribues */})
this works for me, and should work for you too.
UPDATE:
you can create a symbol like # (or - or anything else), before the action part in url, to make the url unique, such as:
routes.MapRoute(
"test", // route name
"#{action}", // url and parameters
new {controller = "MyHome", action = "Home"} // parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
in this way, your urls are different from the Default map-route and you can create urls like:
site.com/#Home
site.com/#About
site.com/#Contact
but the first, in my idea, is better and I always use that.
Using the Attribute Routing of MVC5, I did similar to Javad_Amiry answer, by placing a route for each action in HomeController:
public class HomeController : Controller
{
[Route("about")]
public ActionResult About()
{
return View();
}
I think this is more maintainable than placing every action in the global RouteConfig.cs file. Better still to combine Attribute Routing with convention-based routing, so new actions added to controller will work by default without a Route attribute (eg: /Home/action) but can be improved by adding the Route attribute (eg: /action).
You could simply modify the default route and remove the controller bit from the url and specify that it will always be Home in the default values:
routes.MapRoute(
"Default",
"{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Obviously you realize that this limits your application to a single controller which is HomeController as now you no longer have any possibility to set it in your url. Stuffing all the actions in a single controller is a bad practice IMHO and violates a couple of principles like RESTful routing and SRP.
ASP.NET MVC root url’s with generic routing

ASP.NET MVC Routes and Client Specifc Functions

I'm working on an application where a system is being built where multiple "customers" will use the system and 99.9% of the controllers/actions will be the same, just pulling different data, but there are times and places where a custom controller action, or view might be needed.
Right now I'm using a default route similar to the following to get the company name with requests.
routes.MapRoute(
"Default", // Route name
"{company}/{controller}/{action}/{id}",
new {company = "Unknown", controller = "Home",
action = "Index", id = UrlParameter.Optional}
);
This works great as I can have my individual controller actions defined like this
public ActionResult ShowReport(string company)
{
//Actual code goes here..
}
I have a system in place that will get the data segment for this specific company and return the proper view. SO for my 99.9% situation this looks great. What I'm looking for is a solution for when I need to render a different view, or have additional actions that are specific to one company.
I could add in switch or other logic within my action, but that feels overly dirty...
For a specific company you can use something like this and put it before the default action, in this case url has to contain Company1/somethingcontroller/etc/etc.
routes.MapRoute(
"Company1Default", // Route name
"Company1/{controller}/{action}/{id}",
new {company = "Company1", controller = "DefaultControllerForCompany1",
action = "Index", id = UrlParameter.Optional}
);
While I actually lean to Jay's answer pertaining to the use of data within models, I think that there is another option. Be warned that I haven't played this all the way out and don't have a full understanding of your application...
Why would you want to hardcode a company name within your global.asax? I don't think that it would be very scalable. If you want to add support for an additional 10 companies, you'd have to create 10 new entries. Also, what if you want to change the name of a company because of a buyout or something? More maintenance.
Why not add a route to send every company to the same controller like...
routes.MapRoute(
"CompanyRouting", // Route name
"{companyname}/{action}",
new { controller = "MySingleCompanyControllerName", action = "Index", companyname = UrlParameter.Optional }
);
MySingleCompanyController.cs
Once in your controller you can just get whatever the companyname value is whenever you want it.
public ActionResult Index()
{
ViewData["companynamevalue"] = RouteData.Values["companyname"];
return View();
}
Index.aspx
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Index</title>
</head>
<body>
<div>
Requested Company Name = <%: ViewData["companynamevalue"] %>
</div>
</body>
</html>
NOTE: One more thing to look into for routing help is Phil Haack's routing debugger.
Defining which view is return from an action is easy:
return View("Index");
This will return the view named "Index" no matter which action is invoked.
On the other hand, both views and actions must be defined at compile time, so you cannot dynamically create them (from what i know of).
I might suggest implementing the Command Pattern in your action to get exactly what you would want for individual companies from a single action.

Why does Html.BeginForm generate empty action?

I have a controller in an area called Admin
public class SiteVisitController : Controller
{
public ViewResult ReadyForCompletion() { ... }
public ViewResult CompleteAndExport() { ... }
}
and a view (ReadyForCompletion.cshtml) that has posts back to a different controller action on the same class
#using (Html.BeginForm( "CompleteAndExport", "SiteVisit" ))
{
<input type="submit" value="Complete & Export" />
}
The generated HTML for this form has a blank action:
<form action="" method="post"> <input type="submit" value="Complete & Export" />
</form>
I want to know why this has a blank action? For more info, I also added in a
#Url.RouteUrl(new { controller = "ReadyForCompletion", action = "SiteVisit", area = "Admin" })
which also printed out an empty string. Also, if I use an empty Html.BeginForm() it generates the correct action.
Registered routes are
context.MapRoute(
"Admin_manyParams",
"Admin/{controller}/{action}/{id}/{actionId}",
new { action = "Index", id = UrlParameter.Optional, actionId = UrlParameter.Optional }
);
I believe your problem is caused by having consecutive optional parameters. I was not able to replicate your problem until I changed the route to contain two optional parameters.
See: This article which explains the problem
For those of you encountering this issue using ASP.NET Core the root cause is the same, though the solution is slightly different. I first saw this in Core using multiple default values when calling .MapRoutes(). E.g.
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Foo", action = "Bar" }
);
The workaround is to place the default values into the string template:
routes.MapRoute(
name: "default",
template: "{controller=Foo}/{action=Bar}/{id?}"
);
YMMV.

Resources