ASP.NET MVC Problem with Re-use query parameters - c#

I have this routes:
routes.MapRoute(
"Default",
"user/{userId}/{controller}/{action}",
new {controller = "Home", action = "Index" }
);
routes.MapRoute(
"Short",
"{controller}/{action}",
new { controller = "Home", action = "Index"}
);
My current location in browser:
http://my_site/user/197/UserEvents
On this page there are links:
#Html.ActionLink("Friends", "Index", "Friends")
#Html.ActionLink("Information", "Index", "UserInfo", new { userId = (string)null },null)
MVC re-use query parameters , so the first generated link:
my_site/user/197/Friends
The second link is generated:
my_site/UserInfo?userId=197
Why userId in the second link has a value of 197? Why not have a link:
my_site/UserInfo

I cannot pinpoint the exact location where MVC decides to reuse whatever route values it has at hand, but here's what I use in my projects:
//
// This fixes "ambient values" problem:
// http://stackoverflow.com/questions/2651675/asp-net-mvc-html-actionlink-maintains-route-values
// http://stackoverflow.com/questions/780643/asp-net-mvc-html-actionlink-keeping-route-value-i-dont-want
return new UrlHelper(
new RequestContext(
HttpContext.Current,
new RouteData {
Route = urlHelper.RequestContext.RouteData.Route,
RouteHandler = urlHelper.RequestContext.RouteData.RouteHandler
}), urlHelper.RouteCollection)
.Action(actionName, controllerName, routeValuesWithArea);
The key here is that neither RouteData.DataTokens nor RouteData.Values aren't set, so there's nothing MVC can possibly reuse.

I would probably do something like
http://foo.com/user/events/197
http://foo.com/user/events?userId=197
I find that the more I try to jive with ASP.NET routing conventions the more time I can send developing my app.
public class UserController : Controller
{
public ActionResult Events(long userId)
{
//Do Something...
}
}
public class FriendsController : Controller
{
public ActionResult Index(long userId)
{
//Do Something...
}
}

Related

URL routing requires /Home/Page?page=1 instead of /Home/Page/1

I am trying to build my ASP.NET MVC 4.5 project to use search engine friendly URLs. I am using the following route mapping.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}/{title}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, title = UrlParameter.Optional }
);
The intention is so that I can create URLs like this:
Mysite.com/Home/Page/1/this-title-bit-is-just-for-show
but it fails and I have to use URLs like this:
Mysite.com/Home/Page?page=1
In case it matters, the Controller Action that this link points to is below:
public ActionResult Page(int page)
{
PostModel pm = new PostModel(page);
return View(pm);
}
And I am generating URLs like this:
1
Can someone tell me where I am going wrong?
Instead of
1
Use
1 //instead of page use id here
and change action method as shown :-
public ActionResult Page(int id) //instead of page use id here
{
PostModel pm = new PostModel(id);
return View(pm);
}

MVC Default route with nullable id not working as expected

A simple routing scenario is not working for me.
my route registration looks like this
context.MapRoute(
"Users_default",
"Users/{controller}/{action}/{id}",
new { action = "Index", id= UrlParameter.Optional });
and i am expecting it to honor the requests for
users/profile/
users/profile/1
users/profile/2
with the following controller
public class ProfileController : Controller
{
public ActionResult Index(int? id)
{
var user = id == null ? (UserModel)HttpContext.Session["CurrentUser"] : userManager.GetUserById((int)id);
return View(user);
}
}
it works for users/profile but not for users/profile/1
i've tried few different things but i know the answer must be simple, its just my lack of knowledge, what am i missing here.
i dont want index to appear. i want to use the same method for both users/profile/1 and users/profile/
Then don't put action into your URL.
context.MapRoute(
"Users_default",
"Users/{controller}/{id}",
new { action = "Index", id= UrlParameter.Optional });
The route you have defined will not allow index to be optional because it is followed by another parameter (in this case "id"). Only the last parameter can be optional on all but the default route.
This is because your route interprets as:
{controller: "profile", action: "1"}.
You need to point you details action url explicit, something like this:
users/profile/index/1
You can use Attribute routing
The code would look like
public class ProfileController : Controller
{
[Route("users/profile/{id}")]
public ActionResult Index(int? id)
{
var user = id == null ? (UserModel)HttpContext.Session["CurrentUser"] : userManager.GetUserById((int)id);
return View();
}
}
And you have to modify your RouteConfig
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// This will enable attribute routing in your project
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
So now you can use users/profile for your default behaviour and users/profile/ for a specific profile.

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();
}

Redirect to another controller/Action not registered in route

iam having controller like below which is not registered in routes table
public class InternalController : Controller
{
/*this controller is not registered in routes table*/
public ActionResult Foo()
{
return Content("Text from foo");
}
}
From another controller which is registered in Routes table i want to call/redirect action of previous controller, one which is not registered in routes table.
public class AjaxController : Controller
{
/*this controller is registered in routes table*/
public ActionResult Foo()
{
/*FROM HERE HOW DO I RETURN CONTENTS OF
controller=InternalController, action = Foo
*/
/*
i tried below piece of code but that doesnt seem to work
*/
return RedirectToAction("Foo", "InternalController ");
}
}
Defined Routes (only one item added)
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("Ajax","ajax/{action}",new {
controller="Ajax",
action="Index"
});
}
If you are choosing not to register a route... then you probably have the file/controller in a specific location that will not change.
In that event, just use the "Redirect" method, instead of "RedirectToAction".
For example:
return Redirect("~/Internal/Foo");
Now that you have shown your routes definition, it is obvious that you can never invoke any other controller than AjaxController. You simply forbid them in your routes, so InternalController could never be served. You will have to change your route definition.
Depending on what you want to achieve and how you want your urls to look like you have a couple of possibilities:
Leave the default route
Modify your existing route definition like so:
routes.MapRoute(
"Ajax",
"{controller}/{action}",
new { controller = "Ajax", action = "Index" }
);
You can create RedirectController for redirecting more Url and pages:
public class RedirectController : Controller
{
public ActionResult Index()
{
var rd = this.RouteData.Values;
string controller = rd["controller2"] as string;
string action = rd["action2"] as string;
rd.Remove("controller2");
rd.Remove("action2");
rd.Remove("controller");
rd.Remove("action");
return RedirectToActionPermanent(action, controller, rd);
}
}
And then you can define redirect from old url in routing tables:
routes.MapRoute(
null, // Name
"ajax/foo",
new { controller = "Redirect",
action = "Index",
controller2 = "InternalController",
action2 = "Foo"}
);
This pattern is also useful if you redirect old url to new one. For example:
routes.MapRoute(
null, // Name
"default.aspx", // redirect from old ASP.NET
new { controller = "Redirect",
action = "Index",
controller2 = "Home",
action2 = "Index" }
);

Unable to make ActionLink or RouteLink generate the correct URL

I'm new to ASP.NET MVC (working with version 3) and cannot get ActionLink or RouteLink to work as I'm expecting. In this app, an event can have many activities and I wish to route to them using:
/Event/1/Activity
/Event/1/Activity/Index (same as previous)
/Event/1/Activity/Details/5
The HTML generated by these two helpers always looks like:
/Event/1
Here's the code...
View Razor HTML
#Html.RouteLink("View Sessions", "SubControllerIndex",
new { eventId = Model.Id, controller = "Activity", action = "Index" })
#Html.ActionLink("View Sessions", "Index", "Activity", new { eventId = Model.Id }, null)
Route mappings
routes.MapRoute(
"SubControllerIndex",
"Event/{eventId}/{controller}",
new { controller = "Activity", action = "Index" },
new { eventId = #"\d+" }
);
routes.MapRoute(
"ActivityIndex",
"Event/{eventId}/{controller}/{action}/{id}",
new { controller = "Activity", action = "Index", id = UrlParameter.Optional },
new { eventId = #"\d+", id = #"\d*" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new { id = #"\d*" }
);
Activity controller
public ActionResult Index(long eventId)
{
var activities = _context.Activities.Where(a => a.Event.Id == eventId).ToList();
return View(activities);
}
Any ideas what I'm doing wrong here?
The reason that the routing system generates /Event/1 instead of /Event/1/Activity/Index for the route
routes.MapRoute(
"ActivityIndex",
"Event/{eventId}/{controller}/{action}/{id}",
new { controller = "Activity", action = "Index", id = UrlParameter.Optional },
new { eventId = #"\d+", id = #"\d*" }
);
is because when generating urls the system will not include any default values in the url. In this case the default value of controller is Activity and the default value of action is Index. Thus,
#Html.RouteLink("View Sessions", "SubControllerIndex",
new { eventId = Model.Id, controller = "Activity", action = "Index" })
will generate /Event/1 instead of /Event/1/Activity/Index. If you click on the link you should still go to the Index action method on the ActivityController.
As mrydengren points out, the ActionLink and RouteLink methods will strip out the parameters that are the same as the defaults. The URLs should then point to the action you're pointing to.
If you still would like to use the full URL, you could create a route without the default values and use RouteLink to create your links with that route.
// Either add this at the end as a new route, or replace the one you already have.
routes.MapRoute("ActivityIndexWithoutParams",
"Event/{eventId/controller/action/id},
new{},
new{ eventId = #"\d+", id = #"\d*"}
);
Now you can use the RouteLink method to create the correct route:
#Html.RouteLink("View Sessions", "ActivityIndexWithoutParameters",
new {eventid = Model.Id, controller = "activity", action = "index"}
);
I ran across this when trying (and trying and trying) to find an answer to why my custom RouteBase implementations were not working on outbound routes (the ones that generate URLs) when I switched the project to using areas.
I finally stumbled on the answer while analyzing the MVC source code: you have to make your custom RouteBase implementations implement IRouteWithArea so the framework knows which Area your custom routes belong to.
public class CustomRoute: RouteBase, IRouteWithArea
{
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
// Implement custom virtual path (url) here
}
#region IRouteWithArea Members
public string Area
{
get { return "Store"; }
}
#endregion
}
Remember that routes are 'greedy', and the first match will be the one that gets processed, so you need to lose that first route entry, otherwise the second one will never be reached.
That's the only change I can see you need to make, it's fine otherwise, add an action link to a view as follows;
#Html.ActionLink("View Sessions", "Details", "Activity", new { eventId = 12, id = 22 }, null)
And you should see that generates the correct URL in the link.

Categories

Resources