I want to create an MVC app to generate factsheets, but I'm not sure how to structure the routing and controllers.
It consists of an index page, which acts as a template for the layout of a number of independent panels, each of which contains different types of data.
I want to have a the route template like the following:
/Factsheets/Panels/PanelType?fundId=1&countryId=ABC
so these would be the URLs I'm using:
/Factsheets/Panels/NameAndDatePanel?Afund=1&county=IE
/Factsheets/Panels/AssetsPanel?fund=1&county=IE
I want a Factsheets controller to be able to supply the Panel controller with the configuration object it needs to generate the type of panel I request.
What should my routing structure look like?
What should my controller structure look like?
edit:
What changes if I want to have a roure structure as follows:
I want to have a the route template like the following:
/Factsheets/ContentArea/Panels/PanelType?fundId=1&countryId=ABC
so these would be the URLs I'm using:
/Factsheets/PageTop/Panels/NameAndDatePanel?Afund=1&county=IE
/Factsheets/PageTop/Panels/AssetsPanel?fund=1&county=IE
so that FactsheetsController is instantiated to contain the data needed by a ContentArea to know which data to supply to the panel it needs to generate.
What comes after the ? is the query string so it won't be reflected by the routing engine. So your route might look like this:
routes.MapRoute(
"Default",
"{controller}/{action}/{panelType}",
new { controller = "Factsheets", action = "Panels", panelType = UrlParameter.Optional }
);
And you would have the following controller:
public class FactsheetsController : Controller
{
public ActionResult Panels(string panelType)
{
return View();
}
}
UPDATE:
If the panelType parameter is not optional you could provide a regex constraint:
routes.MapRoute(
"Default",
"Factsheets/Panels/{panelType}",
new { controller = "Factsheets", action = "Panels", panelType = UrlParameter.Optional },
new { panelType = ".+" }
);
Related
ASP.NET MVC provides simple templates for controller methods such as Details, and can have something like:
public ActionResult Details(int id)
{
// do something
}
This can be accessed by: http://localhost:port/Controller/Details/id
What I'm trying to do is instead provide a different type like:
public enum MyEnum
{
All,
Pending,
Complete
}
And then I setup my controller method like:
public ActionResult MyMethod(MyEnum myEnum = MyEnum.Pending)
{
// do something
}
This works fine for: http://localhost:port/Controller/MyMethod/ because it uses the default argument.
To specify a different argument I have to do http://localhost:port/Controller/MyMethod?myEnum=All and that works.
I'm wondering, is it possible for me to be able to do http://localhost:port/Controller/MyMethod/All instead of using ?myEnum=All?
Upon trying to do it that way I get a 404 exception which is understandable, but why doesn't this happen for id in Details?
Can I change the MapRoute which is currently: url: "{controller}/{action}/{id}" to allow me to achieve it with my own type?
What I've tried so far:
I only want this route enforcement for one of my schemes such as http://localhost:port/Controller/MyMethod/{ViewType}, I tried this but it doesn't seem to do anything:
routes.MapRoute(
"MyRoute",
"MyController/Index/{MyEnum}",
new { controller = "MyController", action = "Pending" }
);
/Controller/MyMethod/All will actually work. The problem is with the default route, which will consider All to be the id route parameter, which doesn't line up with what your action is using as a parameter. It would actually work fine if your action signature was:
public ActionResult MyMethod(MyEnum id = MyEnum.Pending)
Since it will then bind All to the right thing.
You could add another route for this use-case, but you'll need to be careful that you don't just create another "default" route, which will take over. In other words, you'll have to fix part of the URL:
routes.MapRoute(
"MyCustomRoute",
"Controller/{action}/{myEnum}",
new { controller = "Controller", action = "MyMethod", myEnum = MyEnum.Pending }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Then, by the mere presence of the /Controller/ prefix to the route, it will use your custom route instead, and fill in All for the myEnum param, rather than hitting the default route and try to fill in id.
However, be advised that when using enums as route params, they must be exact matches. So, while /Controller/MyMethod/All will work, /Controller/MyMethod/all will not. To get around this, you'll have to create a custom model binder. I did a quick search and found the following article which may help you in that regard.
You can indeed. Do not change the default route "{controller}/{action}/{id}", but rather add one before the default. This new one needs to be fairly specific:
routes.MapRoute(
"EnumRoute",
"Controller/MyMethod/{myEnum}",
new { controller = "Controller", action = "MyMethod", myEnum = UrlParameter.Optional }
);
What that basically says is "when you see request to literally Controller/MyMethod/whatever, use this controller and that method and pass whatever as parameter of the request". Note that actual controller does not necessary have to be what route says in the url, although it is a good idea to stick to that.
suppose i have link like www.mysite.com/Certificate/data/C001-P2003
my controller name is Certificate and action name data
public class CertificateController : Controller
{
public ActionResult Data(string certid,string studid)
{
return View();
}
}
i want to know what i need to develop which will extract id and break into two part separated by hyphen and map two data to Data action.
C001 will map to certid and P2003 will map to studid. so i need concept how to do it?
do i need to develop any action filter ?
do i need to write any custom value provider ?
do i need to write any custom model binder ?
i do not want to break the data inside action rather i want to do it out of action. so suggest me something best idea. thanks
Create a convention in your RouteConfig that will map the route you want.
routes.MapRoute(
name: "CertificateData",
url: "Certificate/data/{certid}-{studid}",
defaults: new { controller = "Certificate", action = "Data"}
);
Or enable Attribute Routing and use template on action to match the desired url
// GET Certificate/data/C001-P2003
[Route("Certificate/data/{certid}-{studid}")]
public ActionResult Data(string certid,string studid) {...}
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)
{
// ...
}
}
So I know google can penalize a site if you have the same content on multiple urls... unfortunately, in MVC this is too common i can have example.com/, example.com/Home/ and example.com/Home/Index and all three urls would take me to the same page... so how do I make sure that whenever Index is in the url, that it redirects to the same without the Index and of course the same thing with the Home
Perhaps this little library may be useful for you.
This library is not very convinient in your case, but it should work.
var route = routes.MapRoute(name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
routes.Redirect(r => r.MapRoute("home_index", "/home/index")).To(route);
routes.Redirect(r => r.MapRoute("home", "/home")).To(route);
The way I handle this is for default pages like Index is to simply create an explicit route for only one of them. I.e. "example.com/People" would be the route for People/Index, and there would be no valid page at the url "/example.com/People/Index".
The Home example is unique in that it has potentially three different URLs. Again in this case I'd simply create a route for "example.com" for that Index action, and not support the other two urls. In other words, you would never link to the other forms of the URL, so their absence should never cause a problem.
We use a Nuget package called AttributeRouting to support this. When you specifiy a GET route for a page, it overrides the defaults for MVC.
Using AttributeRouting usually you'd map the index to [GET("")] but for the special case of Home where you also want to also support the root URL that omits the controller name , I think you'd also add an additional attribute with IsAbsoluteUrl:
public class HomeController : BaseController
{
[GET("")]
[GET("", IsAbsoluteUrl = true)]
public ActionResult Index()
{...
So I found a way to do it without any external Library...
In my RouteConfig I had to add these two routes at the top, just below the IgnoreRoute
routes.MapRoute(
"Root",
"Home/",
new { controller = "Redirect", action = "Home" }
);
routes.MapRoute(
"Index",
"{action}/Index",
new { controller = "Redirect", action = "Home" }
);
Then I had to create a new Controller called Redirect and I created a method for each of my other Controllers like this:
public class RedirectController : Controller
{
public ActionResult Home()
{
return RedirectPermanent("~/");
}
public ActionResult News()
{
return RedirectPermanent("~/News/");
}
public ActionResult ContactUs()
{
return RedirectPermanent("~/ContactUs/");
}
// A method for each of my Controllers
}
That's it, now my site looks legit. No more Home, no more Index in my URLs, this of course has the limitation of not being able to accept parameters to any of the Index methods of your Controllers though if it was really necessary, you should be able to tweak this to achieve what you want.
Just an FYI, if you want to pass an argument to your Index Action, then you can add a third route like this:
routes.MapRoute(
name: "ContactUs",
url: "ContactUs/{id}/{action}",
defaults: new { controller = "ContactUs", action = "Index", id = UrlParameter.Optional }
);
This will create a URL like this: /ContactUs/14
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();
}