Make page url to be page title - c#

What would be the easiest way to make a page title to be the url?
Currently I have:
http://localhost:53379/Home/Where
http://localhost:53379/Home/About
http://localhost:53379/Home/What
and would like to have
http://localhost:53379/where-to-buy
http://localhost:53379/about-us
http://localhost:53379/what-are-we
I thought about adding a route to each page (there's only 9 pages) but I wonder if there's something better, for example for big sites.
routes.MapRoute(
name: "Default",
url: "where-to-buy",
defaults: new {
controller = "Home",
action = "Where",
id = UrlParameter.Optional
}
);
...
and I would like to have it in English and Local language as well, so adding more routes would not make that much sense...

If you need to fetch pages dynamically from the database, define a new route which will catch all requests. This route should be defined last.
routes.MapRoute(
name: "Dynamic",
url: "{title}",
defaults: new {
controller = "Home",
action = "Dynamic",
title = ""
}
)
Then in your controller:
public class HomeController {
public ActionResult Dynamic(string title) {
// All requests not matching an existing url will land here.
var page = _database.GetPageByTitle(title);
return View(page);
}
}
Obviously all pages need to have a title (or slug, as it's commonly referred to) defined.
If you have static actions for each page, you could use AttributeRouting. It will allow you to specify the route for each action using an attribute:
public class SampleController : Controller
{
[GET("Sample")]
public ActionResult Index() { /* ... */ }
[POST("Sample")]
public ActionResult Create() { /* ... */ }
[PUT("Sample/{id}")]
public ActionResult Update(int id) { /* ... */ }
[DELETE("Sample/{id}")]
public string Destroy(int id) { /* ... */ }
[Route("Sample/Any-Method-Will-Do")]
public string Wildman() { /* ... */ }
}
I use it on a mid-sized project and it's working pretty well. The big win is that you always know where your routes are defined.

Related

C# MVC - namespace in multi controller

I want use AnotherController name in SomeController.
But, RoutePrefix Attribute is can only be declared by Controller level.
Prepare the following.
namespace KRSMART.Controllers
{
public class SomeController : Controller
{
/* localhost:0000/Some/Index */
public ActionResult Index()
{
return View();
}
/* I want Url */
/* localhost:0000/Another/Test */
[Route("Another/Index")]
public ActionResult Test()
{
return View();
}
}
}
It didn't work as I wanted it to.
I know I can create a new controller and do it, but I didn't want to.
I'd like to get some advice from you who are familiar with Route.
You need to add routes.MapMvcAttributeRoutes(); before default route register,
your RegisterRoutes function should be like,
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 }
);
}

How to catch all areas with MapRoute

I am new to MVC and editing an existing application. Currently I see the following in RouteConfig.cs:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Util",
"util/{action}",
new {controller = "util"});
routes.MapRoute(
"Catchall",
"{*url}",
new {controller = "Main", action = "CatchUrl"});
}
}
Inside the Main controller there is logic on that basically does a RedirectToRoute and sets the area, controller, action, and querystring called location to a certain value.
public class MainController : Controller
{
public ActionResult CatchUrl()
{
var agencyId = 9;
var routeValues = new RouteValueDictionary
{
{"area", "area1"},
{"controller", "dashboard"},
{"action", "index"},
{"location", "testLocation"}
};
return RedirectToRoute(routeValues );
}
}
This seems to work fine, when you give it an invalid area it correctly goes to the default one.
I also see a file called CustomAreaRegistration.cs:
public abstract class CustomAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
AreaName + "Ajax",
AreaName + "/{controller}/{action}",
new { action = "Index" }
);
context.MapRoute(
AreaName + "ShortUrl",
AreaName + "/{controller}",
new {action = "Index"}
);
}
}
I am having trouble understanding how the Area routing works and how it knows how to go to the correct controller.
Furthermore, I am trying to get it so that when you visit
/{area}/ it does some logic and redircts you to the correct controller. Similar to how CatchUrl works
My attempt:
routes.MapRoute(
"AreaIndex",
"{module}/",
new {controller = "Main", action = "Index"});
MainController :
public class MainController : Controller
{
public ActionResult Index()
{
var requestHost = HttpContext.Request.Url?.Host;
var location= requestHost == "localhost" ? Request.QueryString["location"] : requestHost?.Split('.')[0];
var routeValues = new RouteValueDictionary
{
{"area", ControllerContext.RouteData.Values["module"]},
{"controller", "dashboard"},
{"action", "index"},
{"location", location}
};
return RedirectToRoute(routeValues );
}
public ActionResult CatchUrl()
{
var routeValues = new RouteValueDictionary
{
{"area", "area1"},
{"controller", "dashboard"},
{"action", "index"},
{"location", "testLocation"}
};
return RedirectToRoute(routeValues );
}
}
And I get
No route in the route table matches the supplied values.
I am not sure why CatchUrl works and mine does not.
I actually don't get what you're asking, but by just looking at the code, that's not the standard way to create/use Areas in MVC 3,4 and 5.
You shouldn't need to write logics inside each controller and do the redirects.
In my RouteConfig, I usually just have the default route mapping. And when you have the needs for Areas, you can right click on the MVC web project in Visual Studio and click'Add -> Area'. That will create a folder with the area name inside an Areas folder right under the root of the web project. And within the area folder, you should find the AreaRegistration.cs for the area name and mappings.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "dashboard", action = "index", id = UrlParameter.Optional },
namespaces: new[] { "Company.Project.Web.UI.Controllers" }
);
}
}
And let's say you want to create an area called 'Admin':
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"admin_default",
"admin/{controller}/{action}/{id}",
new { action = "index", id = UrlParameter.Optional },
namespaces: new[] { "Company.Project.Web.UI.Areas.Admin.Controllers" }
);
}
}
Lastly, I think a screenshot might be helpful.
-- updates --
Based on the comments, if you want the route /apple?location=home to go to Apple Controller and its Home method, while the route /orange?location=dashbard to go to Orange Controller and its Dashboard method, it's better to define a route in RouteConfig.
Ideally you wish you can have something like this in RouteConfig:
routes.MapRoute(
name: "Area-CatchAll",
url: "{area}?location={controller}"
);
But that's not possible as MVC will error out saying "The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.".
Instead, you can direct the traffic to a controller and you can define the area and location as parameters.
routes.MapRoute(
name: "Area-CatchAll",
url: "{area}",
defaults: new { controller = "Area", action = "Translate" }
);
public class AreaController : Controller
{
// The {area} from route template will be mapped to this area parameter.
// The location query string will be mapped to this location parameter.
public ActionResult Translate(string area, string location)
{
// This also assumes you have defined "index" method and
// "dashboard" controller in each area.
if (String.IsNullOrEmpty(location))
{
location = "dashboard";
}
return RedirectToAction("index", location, new { area = area });
}
}
Again, I wouldn't create route like that to redirect traffic to areas, if I don't have to.
I am trying to get it so that when you visit
/{area}/ it does some logic and redircts you to the correct controller. Similar to how CatchUrl works
First of all, lets be clear about this. The catch-all route is not routing it is redirecting. It is issuing an HTTP 302 response to the browser, which tells the browser to lookup a new location on your server. This sort of thing is not very efficient because it requires an extra round trip across the network, but it is the only way to get the URL in the browser to change from the server side (if that is what you intend to do).
Routing, on the other hand, is taking the initial request and sending it directly to a specific resource (controller action) without the extra round trip across the network and without changing the URL in the browser.
Your routes are already setup to use
/Area/Controller
to route to the correct controller here:
public abstract class CustomAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
AreaName + "Ajax",
AreaName + "/{controller}/{action}",
new { action = "Index" }
);
context.MapRoute(
AreaName + "ShortUrl",
AreaName + "/{controller}",
new {action = "Index"}
);
}
}
(assuming you have a subclass of this for each area that sets the AreaName).
If you indeed want to route (not redirect) the /module/ URL to the DashboardController.Index method in the corresponding Area, you change your ShortUrl to make the controller optional and default it to the DashboardController.
public abstract class CustomAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
AreaName + "Ajax",
AreaName + "/{controller}/{action}",
new { action = "Index" }
);
context.MapRoute(
AreaName + "ShortUrl",
AreaName + "/{controller}",
new { controller = "Dashboard", action = "Index" }
);
}
}
This will send the URL /Foo/ to the Foo area's DashboardController.Index method, but send the URL /Foo/Bar/ to the Foo area's BarController.Index method.
The above assumes that you are diligent enough to ensure that none of your AreaNames are the same as controller names in the non-area part of your project. If they are, you will never be able to reach those non-area controllers without some extra configuration such as a route constraint.
The MainController.Index method isn't needed at all (unless you have some specific reason why you want to change the URL in the browser).

How to Create a dynamic Controller to handle many static Views in ASP.NET MVC?

I have a bunch of mostly-static pages (about 40),
like: order-form01.html, order-form02.html, orderform03.html etc..
Should each of them have its own Controller/Action, or is that possible to have one dynamic Controller/Action for all of them?
My Url should look like this: http://MyProject/GlobalController/IndividualView and for the above example: http://MyProject/OrderForm/order-form01, http://MyProject/OrderForm/order-form02 etc..
Thanks in advance.
Yes it's very easy AND you don't need a switch statement or any other redundant logic.
public class MyController
{
public ActionResult Page(string file)
{
return View(file);
}
}
The magic is in the Route Map:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// New MapRoute for your 40+ files..
routes.MapRoute(
"OrdeForm",
"OrderForm/{file}",
new { controller = "MyController", action = "Page", {file} = "" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
Additionally:
I pass the View name in the query string.
Is not required, but is supported. The following urls will work:
// Url Parameters
http://MyProject/OrderForm/order-form01
http://MyProject/OrderForm/order-form02
// Querystring Parameters
http://MyProject/OrderForm?file=order-form01
http://MyProject/OrderForm?file=order-form02
The only catch is that you need to rename your html files to cshtml and place them in the correct directory for the ViewEngine to find.
#Erik, I also bit of new to mvc . Could you please explain your route map as of how is it possible with default raute again and again
Routes are broken down into 3 values:
Controller
Action
Parameter(s)
At a bare minimum, the controller and action are required. Where the values come from is not dependent on the Url. For example, in the following Url and Map Route...
// Url
http://MyProject/
// MapRoute
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = "" }
);
// Controller named "Home" matches the default in the above route
// Method named "Index" matches the default in the above route
public class HomeController {
public ActionResult Index() {
return new EmptyResult();
}
}
... everything still works because we provided a default value for the controller and action.
Ok let's break down the URL you want:
http://MyProject/OrderForm/order-form01
http://MyProject/OrderForm/order-form02
http://MyProject/<identifier>/{parameter}
You have one identifier that tells me route (OrderForm) and one changing value that because it changes and you want one value, should be a parameter.
http://MyProject/<identifier>/{file}
The name of the parameter makes no difference as long as it matches the signature of the controller method:
http://MyProject/{Controller}/{file}
public class HomeController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
or
http://MyProject/{Controller}/{einstein}
public class HomeController {
public ActionResult Index(string einstein) {
return new EmptyResult();
}
}
I named the parameter file, because it tells me it's the parameter is a name of a file, whereas the name einstein has no inherent description so is a terrible name for a variable.
http://MyProject/{Controller}/{file}
// MapRoute
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = "" }
);
// Controller named "Home" matches the default in the above route
// Method named "Index" matches the default in the above route
public class HomeController {
public ActionResult Index() {
return new EmptyResult();
}
}
Now we only want this route to run when the identifier is OrderForm so we don't allow that to be a value, we hard code it.
url: "OrderForm/...
Next we have a value that keeps changing, so we to add url parameter:
url: "OrderForm/{file}"
Now we have an issue because we aren't allowing MVC to parse values from the url to populate Controller nor Action so we must supply them.
routes.MapRoute(
name: "",
url: "OrderForm/{file}",
defaults: new { controller = "Home", action = "Index", file = "" }
);
Here we've mapped the url http://MyProject/OrderForm/{file} to:
public class HomeController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
Now I would choose to to update the defaults to something more specific and descriptive:
routes.MapRoute(
name: "",
url: "OrderForm/{file}",
defaults: new { controller = "OrderForm", action = "Index", file = "" }
);
public class OrderFormController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
Hope that all makes sense.
After the question edited :my solution is, you can have one controller/action and it should call view (cshtml). Your querystring data should be pass to view as of viewbag variable and partial views should be called acording to the viewbag variable. noo need of editing routing table even(if you are willing to pass it as a query string).
//your routeconfig will be
routes.MapRoute(
name: "default",
url: "{controller}/{file}",
defaults: new { controller = "OrderForm", action = "Index", file = "" }
);
//here no need to have 40 routing table one is enough
//your controller/action will be
public class OrderForm
{
public ActionResult Index(string file)
{
ViewBag.Orderform=file
return View(file);
}
}
//view bag variable is accessible in view as well as in javascript
But I would say as best practice, you can modify default routing to access all urls and navigate it to same controller/action and let that action to return the view. After that use angular / knockout js to handle client side routing and based on it the partial views should be loaded.(still your url will be different for your 40 pages but noo need to pass it as query string)
//your route table will be
routes.MapRoute(
name: "default",
url: "{controller}/{file}",
defaults: new { controller = "OrderForm", action = "Index"}
);
//your controller will be
public class OrderForm
{
public ActionResult Index()
{
return View(file);
}
Navigation should be handled by client side routing

ASP.NET MVC actionresult multiple pages

I currently have a Products controller with a hardcoded 'Product' actionresult per product (as the products are fixed and do not change):
site.com/Products/Product
site.com/nl/Products/Product
This results in a single page per product containing all information.
Now I would like to create multiple pages per product to highlight some features or options instead of showing a single product page.
e.g.:
site.com/nl/Products/Product/Detail1
site.com/nl/Products/Product/Option2
site.com/nl/Products/Product/Option16
What is the best way to do this?
Should I create e.g. ProductDetail1 action and a ProductOption2 action?
You can responce different views in a single action
public class ProductsController : Controller
{
public ActionResult Product(int id, string view,)
{
Product prod = Context.GetProduct(id);
if(!string.IsNullOrEmpty(view))
{
switch(view.ToLower()){
case "detail": return View("Detail", prod.Detail);
case "option1": return View("Option1", prod.GetOption(1));
case "option2": return View("Option2", prod.GetOption(2));
}
}
return View();
}
}
Well you have your Controller Method:
public class ProductsController : Controller
{
public ActionResult Product(string option)
{
//here your logic
return View();
}
}
You can change your Default Route:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{option}",
defaults: new { controller = "Home", action = "Index", option = UrlParameter.Optional }
);
Now if you call url like site.com/nl/Products/Product/Detail1 option object in your controller will have value Detail1. And you can do anything what whould you like with this param.

ASP.Net MVC 4 WebAPI Custom Routing

I have a controller called News in my WebAPI project, I have two default actions called Get that handled the following URL's:
/api/News <- this returns a list of news
/api/News/123 <- this returns a specific news item by id.
All straightforward so far and obviously the default route handles these scenarios. I next want to have a URL that looks like this:
/api/News/123/Artists <- will return all artists related to the specified news item.
Now I am fairly news to ASP.Net MVC and WebAPI so this is the first time I have had to deal with routing. I have modified my default route to look like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{id}/{action}",
defaults: new { controller = "News", action = "Get", id = UrlParameter.Optional }
So here I have moved the {action} to the end of the URL and I have added a Artists method to my News controller. This still works with the first two scenarios but returns a 404 for the third scenario.
Obviously the routing isn't working for /api/News/123/Artists but I have no idea why.
I can't seem to find any examples of people using WebAPI like this which makes me think I am doing something fundamentally wrong.
Any help would be appreciated.
The issue is, that you are trying to acces Web API but mapping the ASP.NET MVC
this is a mapping you need:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new {controller = "News", action = "Get", id = RouteParameter.Optional}
);
And it should be done in the App_Start \ WebApiConfig (if using the default template settings)
Example of the methods (in your news API controller):
// GET api/values/5
public string Get(int id)
{
return "value " + id;
}
// GET api/values/5
[HttpGet]
public string Artist(int id)
{
return "artist " + id;
}
The AttributeRouting should be a good solution. It can be installed by Nuget, and the document is here.
Some examples
public class SampleController : Controller
{
[GET("Sample")]
public ActionResult Index() { /* ... */ }
[POST("Sample")]
public ActionResult Create() { /* ... */ }
[PUT("Sample/{id}")]
public ActionResult Update(int id) { /* ... */ }
[DELETE("Sample/{id}")]
public string Destroy(int id) { /* ... */ }
[Route("Sample/Any-Method-Will-Do")]
public string Wildman() { /* ... */ }
[GET("", ActionPrecedence = 1)]
[GET("Posts")]
[GET("Posts/Index")]
public ActionResult Index() { /* ... */ }
[GET("Demographics/{state=MT}/{city=Missoula}")]
public ActionResult Index(string state, string city) { /* ... */ }
}
It works very well about custom routing.
Update
In asp.net WebApi 2, AttributeRouting is included inside by native. It has some history, the first version, asp.net WebApi 1, is weak about routing annotations.
And then, asp.net WebApi 2 is released, the AttributeRouting is included by native. So, that open project is not maintained anymore, said in GitHub page.
In microsoft blog, the section Independent Developer Profile – Tim McCall – Attribute Routing in MVC and Web API 2 said about the history too.
In routing Action is the action name on the method that you want to route to .That action name should be in the attribute used on the method.
[HttpGet]
[ActionName("CustomAction")]
public HttpResponseMessage MyNewsFeed(Some params)
{ return Request.CreateResponse(); }
Now your route should look like this
routes.MapRoute(
name: "Default",
url: "{controller}/{id}/{action}",
defaults: new { controller = "News", action = "CustomAction", id = UrlParameter.Optional
Let me know if this helps.

Categories