I have an hybrid application which is classic asp.net and MVC both. It is asp.net 4.5 and MVC 4.
Problem:
From the route, i can get to the action just fine but the guid is always coming up as null. Do you see any thing that i may be missing in my implementation? Problem is with SiteMyHome route.
Route
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
//Ignore calls to httphandlers
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
RouteConstants.SiteMyHome,
RouteConstants.SiteMyHome + "/{guid}/{ccode}/{tab}",
new { controller = ControllerNames.SiteRouter, action = ActionNames.SiteMyHome, ccode = UrlParameter.Optional, tab = UrlParameter.Optional }
);
//Set the default route
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Action
public ActionResult SiteMyHome(Guid? guid, string ccode, string tab)
{
return null;
}
Test
/SiteMyHome/9c95eb0-d8fb-4176-9de0-5b99f8b914db/test/matched
When i debug my action, i get the following
guid = null
ccode = test
tab = matched
Why is guid getting passed as null here?
Now, when i change the action to string guid then i get its value just fine (image attached). I am confused...
public ActionResult SiteMyHome(string guid, string ccode, string tab)
It was a guid issue. The guid that i was testing with is not a valid guid where as d04e9071-0cdf-4aa8-8f34-b316f9f3c466 is resulting in a valid guid.
Related
In my MVC5 application I am trying to pass a string to an action.
In PodcastsController I have an action called Tagged:
public ActionResult Tagged(string tag)
{
return View();
}
For example, if I wanted pass the string Testto the Tagged action, it would look like this in the url:
/Podcasts/Tagged/Test
I have a route set up like this:
routes.MapRoute(
"Podcasts",
"Podcasts/Tagged/{tag}",
new { controller = "Podcasts", action = "Tagged", tag = UrlParameter.Optional }
);
Edit I am calling the Tagged action like this:
if (!string.IsNullOrEmpty(tagSlug))
{
return RedirectToAction("Tagged", "Podcasts", new { tag = tagSlug });
}
When I set a break point on the Taggedaction, tag is always null
Can anyone see what I am doing wrong?
I'm pretty sure there's something wrong with the route but I can't figure out what...
The reason you are getting a null is because you are not actually passing it a parameter called tag in your code:
if (!string.IsNullOrEmpty(tagSlug))
{
return RedirectToAction("Tagged", "Podcasts", new { tagSlug });
}
When you omit the property name it takes the variable name, so you are actually passing a variable tagSlug which your action does not accept. Try this instead:
if (!string.IsNullOrEmpty(tagSlug))
{
return RedirectToAction("Tagged", "Podcasts", new { tag = tagSlug });
}
It must be working exacly like this....
Then go to http://localhost:64147/podcast/tagged/hi and recive hi as a value of tag
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "MyRoute",
url: "podcast/tagged/{tag}",
defaults: new { controller = "Podcasts", action = "Tagged", tag = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I'm sorry i'm asking here, but did you check that your route precedes any other routes that could overlay?
The routing is done by taking them in order. The first route that corresponds to the given parameters is chosen so if you have this route first
{controller}/{action}/{id}
and then
Podcasts/Tagged/{tag}
you will get null because the first route is chosen
Route order actually matters
How can i do like this url (http://www.domain.com/friendly-content-title) in Asp.Net MVC 4.
Note: This parameter is always dynamic. URL may be different: "friendly-content-title"
I try to Custom Attribute but I dont catch this (friendly-content-title) parameters in ActionResult.
Views:
Home/Index
Home/Video
ActionResult:
// GET: /Home/
public ActionResult Index()
{
return View(Latest);
}
// GET: /Home/Video
public ActionResult Video(string permalink)
{
var title = permalink;
return View();
}
RouteConfig:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Home Page",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
routes.MapRoute(
name: "Video Page",
url: "{Home}/{permalink}",
defaults: new { controller = "Home", action = "Video", permalink = "" }
);
}
What should I do for catch to url (/friendly-content-title)?
To enable attribute routing, call MapMvcAttributeRoutes during configuration. Following are the code snipped.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
}
}
In MVC5, we can combine attribute routing with convention-based routing. Following are the code snipped.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
It is very easy to make a URI parameter optional by adding a question mark to the route parameter. We can also specify a default value by using the form parameter=value. here is the full article.
Radim Köhler's solution is a good one.
But another option if you want more control over routing is using a custom constraint.
Here's an example
RouteConfig.cs
routes.MapRoute(
"PermaLinkRoute", //name of route
"{*customRoute}", //url - this pretty much catches everything
new {controller = "Home", action = "PermaLink", customRoute = UrlParameter.Optional},
new {customRoute = new PermaLinkRouteConstraint()});
So then on your home controller you could have action like this
HomeController.cs
public ActionResult PermaLink(string customRoute)
{
//customRoute would be /friendly-content-title..do what you want with it
}
The magic of this happens in the IRouteConstraint that we specified as the 4th argument in the MapRoute call.
PermaLinkRouteConstraint.cs
public class PermaLinkRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
var permaRoute = values[parameterName] as string;
if (permaRoute == null)
return false;
if (permaRoute == "friendly-content-title")
return true; //this indicates we should handle this route with our action specified
return false; //false means nope, this isn't a route we should handle
}
}
I just wanted to show a solution like this to show you can basically do anything you want.
Obviously this would need to be tweaked. Also you'd have to be careful not to have database calls or anything slow inside the Match method, as we set that to be called for every single request that comes through to your website (you could move it around to be called in different orders).
I would go with Radim Köhler's solution if it works for you.
What we would need, is some marker keyword. To clearly say that the url should be treated as the dynamic one, with friendly-content-title. I would suggest to use the keyword video, and then this would be the mapping of routes:
routes.MapRoute(
name: "VideoPage",
url: "video/{permalink}",
defaults: new { controller = "Home", action = "Video", permalink = "" }
);
routes.MapRoute(
name: "HomePage",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
Now, because VideoPage is declared as the first, all the urls (like these below) will be treated as the dynamic:
// these will be processed by Video action of the Home controller
domain/video/friendly-content-title
domain/video/friendly-content-title2
while any other (controllerName/ActionName) will be processed standard way
I have a SiteController class in my MVC4 project,the 'Site' url is working fine, but I need a dynamic url part right after 'Site', I need the url to look like this:
mysite.com/Site/{DYNAMICSTRING}/Users(or whatever)
{DYNAMICSTRING} can be the name of a subsite, so the controller should check if that subsite does actually exist in the database.
Right now I'm using Query strings, but that is not what my client wants.
How can I do that?
Additional details
My routing:
routes.MapRoute(
"Subdomain", // Route name
"{controller}/{action}/{dynamicString}", // URL with parameters
new { controller = "Site", action = "Subdomain" } // Parameter defaults
);
My controller:
public ActionResult Subdomain(string dynamicString)
{
return View();
}
the value of dynamicString is null when I navigate to: /Site/Subdomain/SomeString
You have to configure routing. For example if you have Homecontroller:
public class HomeController:Controller
{
public ActionResult Subdomain(string dynamicString)
{
return View();
}
}
then you have to configure your routing something like that
routes.MapRoute(
"Subdomain", // Route name
"{controller}/{action}/{dynamicString}/anyOtherParams", // URL with parameters
new { controller = "Home", action = "Subdomain", dynamicString = "" } // Parameter defaults
);
You can do it like this:
routes.MapRoute(
name: "Default", // Route name
url:"Site/{dynamicstring}", // URL with parameters
defaults: new {controller = "Site", action = "Index" } // Defaults
);
you can keep adding parts to the url part like so
url:"Site/{dynamicstring}/{anythingelse}" // url:"Site/{dynamicstring}/{anythingelse}/{bla}/Test/{hello}/etc..."
or you can also have a catch all route like this:"
routes.MapRoute(
name: "Default", // Route name
url:"{*all}", // catch all
defaults: new {controller = "Site", action = "Index" } // Defaults
);
and fetch all other parts in your controllers index action, by splitting them on /
Make sure you put the custom route before your default otherwise the default route will pick it up.
in your controller you get something like this:
public ActionResult Index(string dynamicstring, string anythingelse)
{
return View();
}
and if you then pass in a url like this:
http://www.mysite.com/Site/test.nl/jordy
your dynamicstring will have the value "test.nl" and your anythingelse will have "jordy"
I hope this helps
I would like to create custom slugs for pages in my CMS, so users can create their own SEO-urls (like Wordpress).
I used to do this in Ruby on Rails and PHP frameworks by "abusing" the 404 route. This route was called when the requested controller could not be found, enabling me te route the user to my dynamic pages controller to parse the slug (From where I redirected them to the real 404 if no page was found). This way the database was only queried to check the requested slug.
However, in MVC the catch-all route is only called when the route does not fit the default route of /{controller}/{action}/{id}.
To still be able to parse custom slugs I modified the RouteConfig.cs file:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
RegisterCustomRoutes(routes);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { Controller = "Pages", Action = "Index", id = UrlParameter.Optional }
);
}
public static void RegisterCustomRoutes(RouteCollection routes)
{
CMSContext db = new CMSContext();
List<Page> pages = db.Pages.ToList();
foreach (Page p in pages)
{
routes.MapRoute(
name: p.Title,
url: p.Slug,
defaults: new { Controller = "Pages", Action = "Show", id = p.ID }
);
}
db.Dispose();
}
}
This solves my problem, but requires the Pages table to be fully queried for every request. Because a overloaded show method (public ViewResult Show(Page p)) did not work I also have to retrieve the page a second time because I can only pass the page ID.
Is there a better way to solve my problem?
Is it possible to pass the Page object to my Show method instead of the page ID?
Even if your route registration code works as is, the problem will be that the routes are registered statically only on startup. What happens when a new post is added - would you have to restart the app pool?
You could register a route that contains the SEO slug part of your URL, and then use the slug in a lookup.
RouteConfig.cs
routes.MapRoute(
name: "SeoSlugPageLookup",
url: "Page/{slug}",
defaults: new { controller = "Page",
action = "SlugLookup",
});
PageController.cs
public ActionResult SlugLookup (string slug)
{
// TODO: Check for null/empty slug here.
int? id = GetPageId (slug);
if (id != null) {
return View ("Show", new { id });
}
// TODO: The fallback should help the user by searching your site for the slug.
throw new HttpException (404, "NotFound");
}
private int? GetPageId (string slug)
{
int? id = GetPageIdFromCache (slug);
if (id == null) {
id = GetPageIdFromDatabase (slug);
if (id != null) {
SetPageIdInCache (slug, id);
}
}
return id;
}
private int? GetPageIdFromCache (string slug)
{
// There are many caching techniques for example:
// http://msdn.microsoft.com/en-us/library/dd287191.aspx
// http://alandjackson.wordpress.com/2012/04/17/key-based-cache-in-mvc3-5/
// Depending on how advanced you want your CMS to be,
// caching could be done in a service layer.
return slugToPageIdCache.ContainsKey (slug) ? slugToPageIdCache [slug] : null;
}
private int? SetPageIdInCache (string slug, int id)
{
return slugToPageIdCache.GetOrAdd (slug, id);
}
private int? GetPageIdFromDatabase (string slug)
{
using (CMSContext db = new CMSContext()) {
// Assumes unique slugs.
Page page = db.Pages.Where (p => p.Slug == requestContext.Url).SingleOrDefault ();
if (page != null) {
return page.Id;
}
}
return null;
}
public ActionResult Show (int id)
{
// Your existing implementation.
}
(FYI: Code not compiled nor tested - haven't got my dev environment available right now. Treat it as pseudocode ;)
This implementation will have one search for the slug per server restart. You could also pre-populate the key-value slug-to-id cache at startup, so all existing page lookups will be cheap.
public class PostController : YourDefinitionController
{
public ActionResult Test(int id ,int title)
{
return View();
}
}
#Html.ActionLink("Postss", "Test","Post" ,new { title = "asdf",id=3 },null)//in Razor view
// here is route registration
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Post",
"{Post}/{Test}/{title}/{id}",
new { controller = "Post", action = "Test",id=UrlParameter.Optional, title=UrlParameter.Optional }
);
routes.MapRoute(
"Defaultx", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I expected to see link like /Post/Test/asdf/3 but it is /Post/Test/3?title=3
Why ? How can i fix it?
I would suggest cleaning your code a bit, because there are many things done based on conventions. So keeping code consistent often helps.
public ActionResult Test(string title ,int id) // Order is switched and title changed to string
Edit: The problem is with wrong route path. You have to change it to "Post/Test/{title}/{id}"
// changed route path expression
routes.MapRoute(
"Post",
"Post/Test/{title}/{id}",
new { controller = "Post", action = "Test", title = UrlParameter.Optional, id = UrlParameter.Optional }
);
Btw: If you are going to play with routes a bit more, Phil Haack's blog will be a great resource.