Action Parameter Naming - c#

Using the default route provided, I'm forced to name my parameters "id". That's fine for a lot of my Controller Actions, but I want to use some better variable naming in certain places. Is there some sort of attribute I can use so that I can have more meaningful variable names in my action signatures?
// Default Route:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
// Action Signature:
public ActionResult ByAlias(string alias)
{
// Because the route specifies "id" and this action takes an "alias", nothing is bound
}

Use the [Bind] attribute:
public ActionResult ByAlias([Bind(Prefix = "id")] string alias) {
// your code here
}

This still works, your query string will just look like "/Controller/ByAlias?alias=something".

You can customize the routes with whatever identifiers you like..
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{alias}", // URL with parameters
new { controller = "Home", action = "Index", alias = "" } // Parameter defaults
);
Edit: Here's an overview from the ASP.NET site

Just because your route uses the name "id" for the ID variable doesn't mean that you have to use the same name in your controller action methods.
For example, given this controller method...
public Controller MailerController
{
public ActionResult Details(int mailerID)
{
...
return View(new { id = mailerID });
}
}
...and this action method call from the view...
<%= Html.ActionLink("More Info", "Details", new { mailerID = 7 }) %>
...you can use whatever naming convention you wish for the ID parameter in your controller action methods. All you need to do is resolve the new name to the default, whether it's "id", "alias", or whatever.
The above example should resolve to :
More Info

Related

Multiple controllers, same same controller name

I was wondering if this is possible. Say I have a monolithic Controller, ReportController.cs.
I want to make a totally separate controller file but still keep the /Report/ in the url that we've some to know and expect.
What I tried was this in my global asax:
routes.MapRoute(
"Testing", // Route name
"{test}/{action}/{id}" // URL with parameters
);
and I added a new Controller named ReportTest.cs
the original route looks like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "LandingPage", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Every time I try to call the simple action I have in ReportTest.cs I get this error: "Value cannot be null or empty. Parameter name: controllerName"
Am I misunderstanding how this works. When you have "{controller}/.." is this not saying 'look for any controllers named + controller and use that'. So if I go to .../Report/DoStuff it'll look for the method DoStuff on ReportController right?
So wouldn't my other route just append a search sequence? So if I put .../Report/DoStuff it'll look for the method DoStuff on ReportController and ReportTest right?
The routing format string:
{controller}/{action}/{id}
Means: the first part ("part" being "element after splitting on /") of the request URI is the controller name, the next part the action method and the last part the ID.
The placeholders {controller} and {action} are special. So your route {test}/{action}/{id} will not find any controller, as none is specified, and {test} doesn't mean anything. (Well it does, it'll get added as a route attribute named "test", and assigned a value representing that part of the request URI, so that is irrelevant for this scenario).
If you want to route an URI to a controller that is not mentioned in the URI, then you must literally specify the prefix, and the controller it should be routed to.
So your routing will look like this:
routes.MapRoute(
"SecondReportController",
"Report/NotOnReportController/{id}",
new { controller = "NotAReportController" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "LandingPage", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Then you can use the following controller:
public class NotAReportController : Controller
{
public ActionResult NotOnReportController(int id)
{
// ...
}
}
You can of course also use attribute routing instead:
public class NotAReportController : Controller
{
[Route("Report/NotOnReportController/{id}")]
public ActionResult NotOnReportController(int id)
{
// ...
}
}

Passing two parameters to controller with #Hml.ActionLink, but the second parameter value is always null

In an ASP.Net MVC 5 application, I use the #Hml.ActionLink helper to call an action on a controller, where I need to pass two parameters. However, the second parameter always ends up with a null value.
Here is the view code, with ActionLink:
#Html.ActionLink(
linkText: "Remove",
actionName: "DeleteItemTest",
controllerName: "Scales",
routeValues: new
{
itemID = 1,
scaleID = 2
},
htmlAttributes: null
)
Here is the controller code:
public ActionResult DeleteItemTest(int? itemID, int? scaleID)
{
//...doing something here....
return View();
}
This is the html that ends up on the page:
Remove
In my controller, I then end up with a value of 1 for "itemID", and null for "scaleID". What am I doing wrong?
Update - adding Route configuration based on request:
public static class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.AppendTrailingSlash = true;
routes.LowercaseUrls = true;
// Ignore .axd files.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Ignore everything in the Content folder.
routes.IgnoreRoute("Content/{*pathInfo}");
// Ignore everything in the Scripts folder.
routes.IgnoreRoute("Scripts/{*pathInfo}");
// Ignore the Forbidden.html file.
routes.IgnoreRoute("Error/Forbidden.html");
// Ignore the GatewayTimeout.html file.
routes.IgnoreRoute("Error/GatewayTimeout.html");
// Ignore the ServiceUnavailable.html file.
routes.IgnoreRoute("Error/ServiceUnavailable.html");
// Ignore the humans.txt file.
routes.IgnoreRoute("humans.txt");
// Enable attribute routing.
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
}
I see you're using attribute routing and MapMvcAttributeRoutes; do you have this route mapped? If not, the default route will take precedence and take only the first parameter as the ID.
You'd need to add in a route that expects both parameters.
Something like this would be slapped onto the controller action:
[Route("{itemID:int}/{scaleID:int}", Name = "DeleteItemTest")]
public ActionResult DeleteItemTest(int? itemID, int? scaleID)
Please note that this is not exact code, just something to work from.

how to overload controllers in MVC

I am trying to overload the MVC controllers, how can I overload it properly?
I want to list all companies in my website in a ListCompanies() controller like below
http://localhost:21047/Home/ListCompanies
and I want to add search criteria if user makes a search like below
http://localhost:21047/Home/ListCompanies/sera
"sera" is my search criteria. if search criteria exist, I want to filter my search result according to the search criteria.
here are my controllers
public ActionResult ListCompanies()
{
return View(db.AY_COMPANIES);
}
[ActionName("ListCompaniesFilter")]
public ActionResult ListCompanies(string filter)
{
var filtredCompanies = from c in db.AY_COMPANIES
where c.COMPANY_FULL_NAME.StartsWith(filter)
select c;
return View(filtredCompanies);
}
and here is my ROUTING which behaves not correctly.
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
);
routes.MapRoute(
"Home", // Route name
"{controller}/{action}/{filter}", // URL with parameters
new { controller = "Home", action = "ListCompanies", filter = UrlParameter.Optional} // Parameter defaults
);
}
My MapRoutes are not correct because it doesnt get the search criteria properly. how can I fix this?
You can't overload in this manner because there is dispatch issue due to two routes on having the same three parts, they are essentially the same.
"{controller}/{action}/{id}"
and
"{controller}/{action}/{filter}"
One way is to collapse the two action methods into one
public ActionResult ListCompanies(string filter)
then if filter is null return the full list else return the filtered list.
I am not fond of this approach.
A better yet simple way to fix this is:
1) remove the second route (the one with the filter)
2) change the signature of the action method from
public ActionResult ListCompanies(string filter)
to
public ActionResult ListCompanies(string id)
allowing the first route to match the parameter on name.
3) Since you can't overload methods you'll need to use your ActionName when requesting the filtered result,
http://localhost:21047/Home/ListCompaniesFilter/sera
First these routes are exactly the same:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index",
id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Home", // Route name
"{controller}/{action}/{filter}", // URL with parameters
new { controller = "Home", action = "ListCompanies",
filter = UrlParameter.Optional} // Parameter defaults
);
They are exactly the same because the number of parameters exactly match (what you name parameters nor the parameter type does not change the signature of the http request method).
There are few different ways to make this work. You could write a MapRoute Constraint for Default that would prevent it from working if the controller/action/id matched a specific set of criteria. Probably not the best course of action.
Since
http://localhost:21047/Home/ListCompanies
http://localhost:21047/Home/ListCompanies/sera
http://localhost:21047/Home/ListCompanies/text
http://localhost:21047/Home/ListCompanies/search
are programmatically the same request with {id} having a null value for your method ListCompanies() I would probably write it like:
public ActionResult ListCompanies(string id)
{
var query = db.AY_COMPANIES;
if (!string.IsNullOrWhiteSpace(id))
{
query.Where(c => c.COMPANY_FULL_NAME.StartsWith(id))
}
var model = query.ToList();
return View(model);
}
And remove your "Home" MapRoute, it won't be needed.
What happens if you have only one action with an optional parameter? Something like as the following:
public ActionResult ListCompanies(string filter = null)
{
var filteredCompanies = string.IsNullOrEmpty(filter)
? db.AY_COMPANIES
: from c in db.AY_COMPANIES
where c.COMPANY_FULL_NAME.StartsWith(filter)
select c;
return View(filteredCompanies);
}

Redirect to Action by parameter mvc

I want to redirect to an action in other Controller but it doesn't work
here's my code in ProductManagerController:
[HttpPost]
public ActionResult RedirectToImages(int id)
{
return RedirectToAction("Index","ProductImageManeger", new { id=id });
}
and this in my ProductImageManagerController:
[HttpGet]
public ViewResult Index(int id)
{
return View("Index",_db.ProductImages.Where(rs=>rs.ProductId == id).ToList());
}
It redirect to ProductImageManager/Index without parameter very well(no error) but with above code i get this:
The parameters dictionary contains a null entry for
parameter 'ID' of non-nullable type 'System.Int32' for method
'System.Web.Mvc.ViewResult Index(Int32)' in
'...Controllers.ProductImageManagerController'.
An optional parameter must be a reference type, a nullable type, or be
declared as an optional parameter. Parameter name: parameters
This error is very non-descriptive but the key here is that 'ID' is in uppercase. This indicates that the route has not been correctly set up. To let the application handle URLs with an id, you need to make sure that there's at least one route configured for it. You do this in the RouteConfig.cs located in the App_Start folder. The most common is to add the id as an optional parameter to the default route.
public static void RegisterRoutes(RouteCollection routes)
{
//adding the {id} and setting is as optional so that you do not need to use it for every action
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Now you should be able to redirect to your controller the way you have set it up.
[HttpPost]
public ActionResult RedirectToImages(int id)
{
return RedirectToAction("Index","ProductImageManager", new { id });
//if the action is in the same controller, you can omit the controller:
//RedirectToAction("Index", new { id });
}
In one or two occassions way back I ran into some issues by normal redirect and had to resort to doing it by passing a RouteValueDictionary. More information on RedirectToAction with parameter
return RedirectToAction("Index", new RouteValueDictionary(
new { controller = "ProductImageManager", action = "Index", id = id } )
);
If you get a very similar error but in lowercase 'id', this is usually because the route expects an id parameter that has not been provided (calling a route without the id /ProductImageManager/Index). See this so question for more information.
This should work!
[HttpPost]
public ActionResult RedirectToImages(int id)
{
return RedirectToAction("Index", "ProductImageManeger", new { id = id });
}
[HttpGet]
public ViewResult Index(int id)
{
return View(_db.ProductImages.Where(rs => rs.ProductId == id).ToList());
}
Notice that you don't have to pass the name of view if you are returning the same view as implemented by the action.
Your view should inherit the model as this:
#model <Your class name>
You can then access your model in view as:
#Model.<property_name>
Try this,
return RedirectToAction("ActionEventName", "Controller", new { ID = model.ID, SiteID = model.SiteID });
Here i mention you are pass multiple values or model also.
That's why here i mention that.
return RedirectToAction("ProductImageManager","Index", new { id=id });
Here is an invalid parameters order, should be an action first
AND
ensure your routing table is correct

ASP.NET MVC Routing - Custom routes with blogs

I am creating a blog engine, and I need a custom route, like this:
localhost/blogname/posts/1
Where blogname should be handled by a BlogsController, and posts will be an action.
How would I define such a route?
I don't think you need to define {controller} in your Url if you define it as a constraint. I think this should work:
routes.MapRoute("Default",
"{action}/{id}",
new { controller = "Blogs", action = "Posts" },
new { controller = "Blogs"});
It might cause problems with other routes though, I'm not sure. If it doesn't work, David's answer of http://site.com/blogs/posts/id is the best way to go.
This feels like a weird approach. If you use the default routing in ASP.NET MVC, you would need one controller class per blog--not something you can easily create on the fly.
If you use the classname BlogsController, then the default routing would work for URLs of the form:
/Blogs/SomeAction/123
Maybe this is what you're looking for:
public class BlogsController : Controller
{
public ActionResult List()
{
return View(GetPostsOrSomething());
}
public ActionResult Posts(int id)
{
return View(new BlogViewModel(id));
}
[HttpPost]
public ActionResult Comment(int id, string comment)
{
// do comment
}
}
And your routing would need to look like this:
routes.MapRoute(
"Blogs", // Route name
"{blog}/{action}/{id}", // URL with parameters
new { controller = "Blogs", action = "List", id = UrlParameter.Optional }
);
Note
Bear in mind, this would match default style URLs, and everything might be routed to your BlogsController. Maybe you could consider a regular expression for the {blog} part of the pattern:
routes.MapRoute(
"Blogs", // Route name
"{blog}/{action}/{id}", // URL with parameters
new { controller = "Blogs", action = "List", id = UrlParameter.Optional },
new { blog = "(blogname1|blogname2|blogname3|etc)" }
);
But, this isn't very flexible either. Any time you added a blog to your site, this regular expression would require an update. I would probably reconsider your URL structure--something similar to the default style.
Something like this
routes.MapRoute(
"Blogs", // Route name
"{blogname}/{action}/{id}", // URL with parameters
new { controller = "Blog", action = "Posts", id = UrlParameter.Optional } // Parameter defaults
);
would work, meaning you could have your blog controller like this:
public class BlogController : Controller
{
public ActionResult Posts(string blogname, int id)
{
... get posts based on blog name and id and return view...
}
}
but then if you want a url like localhost/admin/dostuff/1 to go to an admin controller, how will MVC know that you don't just mean a 'blogname' called 'admin'?
You would need to do something like the regex matching that David suggests, or else specifically add a route for any other controllers you have before you add the Blog route
e.g.
routes.MapRoute(
"Admin Controller Routes", // Route name
"admin/{action}/{id}", // URL with parameters
new { controller = "Admin", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Account Controller Routes", // Route name
"account/{action}/{id}", // URL with parameters
new { controller = "Account", id = UrlParameter.Optional } // Parameter defaults
);
... etc - one for each controller ...
I found this approach to be best for my needs. Very simple, goal is to have the title of the blog as the parameter but also the URL (I didn't want a ? to set a parameter value, I have been told it is not helping SEO. Goal is MYURL.com/blog/my-blog-title
ASP.NET MVC
add to your RouteConfig.cs file
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Blog",
url: "blog/{title}",
defaults: new { controller = "Blog", action = "GetBlog" },
constraints: new { title = #"[\w\-]*" }
);
Add a Controller and call it Blog and then add the following Action to that Controller
[Route("blog/{title}")]
public ActionResult GetBlog(string title)
{
// do what ever code you need to do here to get the blog from the title and pass a model to the view using return View(MyBlogObject)
return View();
}

Categories

Resources