Map multiple indexed routes - asp.net-mvc

I have a number of controllers in application:
ApiV1Controller
ApiV2Controller
ApiV3Controller
...
Is it possible to map routes for them with single MapRoute statement to URLs like /api/v1/{action}?

You could write a custom route. Let's suppose that you have the following controllers:
public class ApiV1Controller : Controller
{
public ActionResult Index()
{
return Content("v1");
}
}
public class ApiV2Controller : Controller
{
public ActionResult Index()
{
return Content("v2");
}
}
public class ApiV3Controller : Controller
{
public ActionResult Index()
{
return Content("v3");
}
}
Now write a custom route:
public class ApiRoute : Route
{
public ApiRoute()
: base("api/{version}/{action}", new RouteValueDictionary(new { action = "index" }), new MvcRouteHandler())
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
rd.Values["controller"] = "Api" + rd.GetRequiredString("version");
return rd;
}
}
That could be registered in your Global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("ApiRoute", new ApiRoute());
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
and that's pretty much it. Now you could play with urls:
/api/v1
/api/v2
/api/v3

Related

How to use Route attribute while working with areas in mvc?

I am working on a project which is having some areas defined in it. I want to use Route attribute to do routing in my project. So I used routes.MapMvcAttributeRoutes() in my route.config file. The following is my route.config file.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "Shopper.Controllers" }
);
}
}
After that I am having two areas in my project like productarea and productcategoryarea. I have global.asax file as below.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
I want to give routes to some of the methods from productcategoryarea and productarea. but when i applies Route("productcategories") on one of methods of productcategoryarea it will show me 404 error.
This is my productcategoryregistration.cs file from productcategoryarea
public class ProductCategoryAreaAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "ProductCategoryArea";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"ProductCategoryArea_default",
"ProductCategoryArea/{controller}/{action}/{id}",
new {controller="ProductCategory", action = "ListOfProductCategory", id = UrlParameter.Optional }
);
}
}
This is my method on which i want to provide route attribute.
[Route("productcategories")]
public ActionResult ListOfProductCategory(ProductCategoryManager productcategorymanager, int page = 1, int pageSize = 4)
{
if (Session["UserName"].ToString() != null)
{
if (Session["UserName"].ToString() == "admin")
{
//returning list of product category
List<ProductCategory> listproductcategories = db.ProductCategories.ToList();
PagedList<ProductCategory> model = new PagedList<ProductCategory>(listproductcategories, page, pageSize);
return View(model);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
return RedirectToAction("Index", "Home");
}
}
So please help me to get out of it.

OData & WebAPI routing conflict

I have a project with WebAPI controllers. I'm now adding OData controllers to it. The problem is that my OData controller has the same name as an existing WebAPI controller, and that leads to an exception:
Multiple types were found that match the controller named 'Member'. This can happen if the route that services this request ('OData/{*odataPath}') found multiple controllers defined with the same name but differing namespaces, which is not supported. The request for 'Member' has found the following matching controllers: Foo.Bar.Web.Areas.API.Controllers.MemberController Foo.Bar.Web.Odata.Controllers.MemberController
And this happens even though the controllers are in different namespaces and should have distinguishable routes. Here is a summary of the config that I have. What can I do (besides renaming the controller) to prevent this exception? I'm trying expose these endpoints as:
mysite.com/OData/Members
mysite.com/API/Members/EndPoint
It seems to me that the URLs are distinct enough that there's gotta be some way to configure routing so there's no conflict.
namespace Foo.Bar.Web.Odata.Controllers {
public class MemberController : ODataController {
[EnableQuery]
public IHttpActionResult Get() {
// ... do stuff with EF ...
}
}
}
namespace Foo.Bar.Web.Areas.API.Controllers {
public class MemberController : ApiControllerBase {
[HttpPost]
public HttpResponseMessage EndPoint(SomeModel model) {
// ... do stuff to check email ...
}
}
}
public class FooBarApp : HttpApplication {
protected void Application_Start () {
// ... snip ...
GlobalConfiguration.Configure(ODataConfig.Register);
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
// ... snip ...
}
}
public static class ODataConfig {
public static void Register(HttpConfiguration config) {
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "OData",
model: GetModel());
}
public static Microsoft.OData.Edm.IEdmModel GetModel() {
// ... build edm models ...
}
}
namespace Foo.Bar.Web.Areas.API {
public class APIAreaRegistration : AreaRegistration {
public override string AreaName {
get { return "API"; }
}
public override void RegisterArea(AreaRegistrationContext context) {
var route = context.Routes.MapHttpRoute(
"API_default",
"API/{controller}/{action}/{id}",
new { action = RouteParameter.Optional, id = RouteParameter.Optional }
);
}
}
}
If you have two controllers with same names and different namespaces for api and OData you can use this code. First add this class:
public class ODataHttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
private readonly Lazy<ConcurrentDictionary<string, Type>> _apiControllerTypes;
public ODataHttpControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
_configuration = configuration;
_apiControllerTypes = new Lazy<ConcurrentDictionary<string, Type>>(GetControllerTypes);
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
return this.GetApiController(request);
}
private static ConcurrentDictionary<string, Type> GetControllerTypes()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var types = assemblies
.SelectMany(a => a
.GetTypes().Where(t =>
!t.IsAbstract &&
t.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase) &&
typeof(IHttpController).IsAssignableFrom(t)))
.ToDictionary(t => t.FullName, t => t);
return new ConcurrentDictionary<string, Type>(types);
}
private HttpControllerDescriptor GetApiController(HttpRequestMessage request)
{
var isOData = IsOData(request);
var controllerName = GetControllerName(request);
var type = GetControllerType(isOData, controllerName);
return new HttpControllerDescriptor(_configuration, controllerName, type);
}
private static bool IsOData(HttpRequestMessage request)
{
var data = request.RequestUri.ToString();
bool match = data.IndexOf("/OData/", StringComparison.OrdinalIgnoreCase) >= 0 ||
data.EndsWith("/OData", StringComparison.OrdinalIgnoreCase);
return match;
}
private Type GetControllerType(bool isOData, string controllerName)
{
var query = _apiControllerTypes.Value.AsEnumerable();
if (isOData)
{
query = query.FromOData();
}
else
{
query = query.WithoutOData();
}
return query
.ByControllerName(controllerName)
.Select(x => x.Value)
.Single();
}
}
public static class ControllerTypeSpecifications
{
public static IEnumerable<KeyValuePair<string, Type>> FromOData(this IEnumerable<KeyValuePair<string, Type>> query)
{
return query.Where(x => x.Key.IndexOf(".OData.", StringComparison.OrdinalIgnoreCase) >= 0);
}
public static IEnumerable<KeyValuePair<string, Type>> WithoutOData(this IEnumerable<KeyValuePair<string, Type>> query)
{
return query.Where(x => x.Key.IndexOf(".OData.", StringComparison.OrdinalIgnoreCase) < 0);
}
public static IEnumerable<KeyValuePair<string, Type>> ByControllerName(this IEnumerable<KeyValuePair<string, Type>> query, string controllerName)
{
var controllerNameToFind = string.Format(CultureInfo.InvariantCulture, ".{0}{1}", controllerName, DefaultHttpControllerSelector.ControllerSuffix);
return query.Where(x => x.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase));
}
}
It drives DefaultHttpControllerSelector and you should add this line at the end of Register method inside WebApiConfig.cs file:
config.Services.Replace(typeof(IHttpControllerSelector), new ODataHttpControllerSelector(config));
Notes:
It uses controller's namespace to determine that controller is OData or not. So you should have namespace YourProject.Controllers.OData for your OData controllers and in contrast for API controllers, it should not contains OData word in the namespace.
Thanks to Martin Devillers for his post. I used his idea and a piece of his code!
You'll want to include namespace constraint on your WebAPI:
var route = context.Routes.MapHttpRoute(
name: "API_default",
routeTemplate: "API/{controller}/{action}/{id}",
defaults:new { action = RouteParameter.Optional, id = RouteParameter.Optional },
);
route.DataTokens["Namespaces"] = new string[] {"Foo.Bar.Web.Areas.API.Controllers"];
If you are getting conflicts for view controllers, you should be able to include a similar namespace constraint as:
routes.MapRoute(
name: "ViewControllers_Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, area = "" },
namespaces: new[]{"Foo.Bar.Web.Controllers"}
);

Asp.net MVC 3 routing area fail

I have this routes:
My website route on WebSite/Global.asax.cs:
namespace WebSite
{
public class MvcApplication : HttpApplication
{
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
...
routes.MapRoute(
"Default",
"Authenticated/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "WebSite.Controllers" }
);
...
}
void Application_Start()
{
...
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
...
}
}
}
My Admin Area route on WebSite/Areas/Admin/AdminAreaRegistration.cs:
namespace WebSite.Areas.Admin
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"qwerty/Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "WebSite.Areas.Admin.Controllers" }
);
}
}
}
My URLs:
WebSite: http://www.mywebsite.com/Authenticated/Controller/Action...
Admin: http://www.mywebsite.com/qwerty/Admin/Controller/Action...
My problem:
With WebSite URL I can call Controllers/Actions from Admin Area, without use "qwerty/Admin", and this is not right. How can I fix this?
Thank you.
Just put this code after each MapRoute. It should work!
.DataTokens["UseNamespaceFallback"] = false;

Access asp.net mvc controller ActionResult

i want to access action result in controller(my controlelr is HotelController action is Index)
(http://localhost:9001/Hotel/Index) it gives below error
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Hotel/Index
Hotel controller
public class HotelController : Base.BoxyController
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
ViewBag.Title = "SonDakka - Otel";
}
public ActionResult Index(string culture)
{
.........
BoxyController
public class BoxyController : MainController
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
..........
MainController
public class MainController : SiteController
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
......
SiteController
[ExitHttpsIfNotRequired]
public class SiteController : Controller
{
public Account Me { get; set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
.......
and this is my global.asax
using System;
using System.Data.Entity;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using Tourism.Data;
using Tourism.Data.Mvc.Authorization;
using Tourism.Data.Mvc.Routing;
namespace Tourism
{
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(TourismContext db, RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
var cultures = db.Cultures.Select(c => c.Code).ToArray();
routes.MapRoute
(
"Ajax",
"{culture}/{controller}/{action}/{id}",
new { id = UrlParameter.Optional },
new { culture = new ArrayRouteConstraint(true, cultures), controller = new ArrayRouteConstraint(true, "Ajax") }
).RouteHandler = new GlobalizedRouteHandler();
routes.Add
(
"Page",
new GlobalizedPageRoute
(
"{culture}/{path}",
null,
new RouteValueDictionary { { "culture", new ArrayRouteConstraint(true, cultures) } },
new GlobalizedRouteHandler()
)
);
routes.Add
(
"Route",
new GlobalizedRoute
(
"{culture}/{path}/{slug}/{id}",
new RouteValueDictionary { { "culture", UrlParameter.Optional }, { "path", UrlParameter.Optional }, { "slug", UrlParameter.Optional }, { "id", UrlParameter.Optional } },
new RouteValueDictionary { { "culture", new ArrayRouteConstraint(false, cultures) } },
new GlobalizedRouteHandler()
)
);
}
protected void Application_Start()
{
Database.SetInitializer<TourismContext>(null);
using (var db = new TourismContext())
{
#if !DEBUG
if (!db.Database.CompatibleWithModel(true))
{
System.Web.HttpRuntime.UnloadAppDomain();
throw new Exception("Veritabanı değişikliği tespit edildi.");
}
#endif
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(db, RouteTable.Routes);
}
}
protected void Application_PostAuthenticateRequest()
{
if (Request.IsAuthenticated)
{
Context.User = System.Threading.Thread.CurrentPrincipal =
new AuthorizationPrincipal(Context.User.Identity);
}
}
}
}
Because this is due to Razor engine unable to find Thanks action in Hotel controller. You need to make a Thanks action with in Hotel controller like this:
public class HotelController : Base.BoxyController
{
public ActionResult Thanks(string culture)
{
return View();
}
}
And also make sure to create a view in Hotel folder with your html code.
Based on the route config you posted, your URL should be with culture, for example:
http://localhost:9001/en/Hotel/Index
Notice the en before Hotel. It could be any value that is valid in your database.

How can I split my controller name for url routing?

I want to split my controller name..
For Example;
My controller name is For_ExpController.cs
and
I want my url like this;
http://localhost/For/Exp/Action
How can I define it on my RouteConfig.cs??
You could write a custom route:
public class MyRoute: Route
{
public MyRoute()
: base(
"{part1}_{part2}/{action}",
new RouteValueDictionary(new { controller = "for_exp", action = "index" }),
new RouteValueDictionary(new { part1 = #"[a-z]+", part2 = #"[a-z]+" }),
new MvcRouteHandler()
)
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
var part1 = rd.GetRequiredString("part1");
var part2 = rd.GetRequiredString("part2");
rd.Values["controller"] = string.Concat(part1, "_", part2);
return rd;
}
}
which will be registered in your Application_Start:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("MyRoute", new MyRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
}

Resources