Can I manipulate the url before routing it, i.e. before MVC goes through my route configuration to find the route to use.
I'd like to replace some characters in the url, for example "www.test.com/ä/ö" to "www.test.com/a/o". That way, if a user typed those letter in the url, the right route would still be used.
Maybe there´s something that I can hook into to manipulate the url?
Edit:
To clarify what I want I'll add an example. Let's say I have a routing configuration that looks like this: "{controller}/{action}". The user types www.test.com/MyCöntroller/MyÄction and I want to route that to the controller "MyController" and the action method "MyAction". I have to do the character replacement before the routing is done, otherwise no matching route will be found. Thus I'd like to replace all "ö" with "o" and all "ä" with "a" (and some more characters) BEFORE the routing is done. Is there any way to do this?
Edit2:
After some research it seems like it is UrlRoutingModule that is the first to get the url in ASP.NET MVC. Maybe there is some way to hook into that?
Take a loot at this post, by creating custom route handler it is possible.
using System.Web.Routing;
namespace My.Services
{
public class MyRouteHander : IRouteHandler
{
ApplicationDbContext Db = new ApplicationDbContext();
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
// Get route data values
var routeData = requestContext.RouteData;
var action = routeData.GetRequiredString("action");
var controller = routeData.GetRequiredString("controller");
//modify your action name here
requestContext.RouteData.Values["action"] = actionName;
requestContext.RouteData.Values["controller"] = "SpecialController";
return new MvcHandler(requestContext);
}
}
}
Check out the answer to this question.
Basically you'll want to use the FilterAttribute with IActionFilter, and then apply the annotation to the ActionResult that services the route. This way you have an intermediary method to manipulate the URL before it's processed by your route configuration.
Related
Is there a way to generate the post URL in the GET action if both the GET and the POST methods share the same name?
The reason for needing to generate the post URL in the controller is that we aren't dealing with the views but only providing the ViewModel that contains a payload in the form of JSON. And the front-end needs to know where to post form values to.
[HttpGet]
public ActionResult MethodName(string id)
{
...
}
[HttpPost]
public JsonResult MethodName(ViewModel model)
{
...
}
e.g. the get URL would be:
/controller/methodname/123
and the post URL would be:
/controller/methodname
in the get action, I need to feed the front-end with the post URL of which the form values would be consumed.
using Url.Action("methodname", "controller", new { id = string.Empty}) would generate a URL that's the same to the post URL, but it's sort of hack-ish way
Another option would be to define a route for the post method and use Url.RouteUrl("PostMethodName") to generate the URL.
Neither of these seem ideal, so is there another (cleaner) way to get this done?
Walter,
I am failing to understand how this is "hack-ish." You are providing a restricted post action with a strongly typed model, the only difference if your action result type. The URL's can be the same if you wish.
Alternativtly you can create just an action for your aJax method such as.
[HttpPost]
public JsonResult ajaxMethod(ViewModel model){
//...
}
This is restrictive to just the HttpPost method and contains your view model. Actions dont need views they simply return an action result. If you wanted to get very bare-bones and action can be an object returning anything. Your route for this action is similar to all other routes:
#Url.Action("ajaxMethod", "controller")
to generate
/controller/ajaxMethod
I may have completly missed the question, please let me know if this doesnt help.
EDIT: Updated from comments.
Just to add if you would like to generate a route or action url in your controller action you can simply use the Url helper in your action such as.
ViewBag.Url = Url.Action("MethodName", "Controller");
Another option is to create a custom Route implementation. A very basic one that includes a line to fix your problem (Drop the existing route parameters) can look like this:
public class CustomRoute : Route
{
public CustomRoute(string url, IRouteHandler routeHandler)
: base(url, routeHandler)
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData == null)
{
return null;
}
/* custom implementation here */
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
/*
* GetVirtualPath is called until a suitable route is found.
* This means modifying the passed RouteValueDictionary has consequenses for subsequent calls.
* Therefore, always work with a copy if you want to modify any values: new RouteValueDictionary(values)
*/
/*
* Drop the existing route parameters. Implicitness can only cause confusion.
*/
requestContext = new RequestContext(requestContext.HttpContext, new RouteData());
return base.GetVirtualPath(requestContext, values);
}
}
You probably also want to have MapRoute methods for your custom route. For this, you just need to look at the original implementation, copy paste it and replace Route with CustomRoute.
I am pretty new to asp.net mvc. I want to get the parameter string in my url
http://localhost/Item/ItemSpec/3431?dep=62&cat=129&tab=2
How can I get the value=3431?
I tried to used HttpContext.Current.Request.QueryString["id"], but it's not work. 3431 is the id of the item that display in my page, that's why I used ["id"].
Thanks you so much.
The 3431 is part of the path of the request, not part of the query string. You could use HttpRequest.Path to get at the path, but MVC routing should allow you to simply write a controller method which accepts the ID as a parameter. I suggest you read up on how to configure routing. (Just searching for ASP.NET routing or MVC routing will give you lots of articles.)
Assuming the default route is configured in Global.asax ({controller}/{action}/{id}) you could have your controller action take an id parameter and the default model binder will automatically set its value:
public ActionResult Foo(string id)
{
...
}
If you want to fetch this id value from some other portion of your code that does have access to an HttpContext you need to fetch it from the RouteData:
var id = HttpContext.Request.RequestContext.RouteData["id"];
RouteData is available in all standard MVC locations. In your example you have used the static HttpContext.Current property which is something that you should never use. I suspect that you are trying to fetch this id from a portion of your code where you are not supposed to have access to the HttpContext. So you'd better fetch this id using standard techniques and then pass it as parameter to other parts of your code.
If Item is your controller, and ItemSpec is action, you can get the Id just by
public ActionResult ItemSpec(int id) { }
You routing have to be setup to:
context.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index" }
);
If you haven't changed your routing so it's still defined as it was when you created Asp.net MVC Web project then this should be one of your controllers:
public class ItemController : ControllerBase
{
...
public ActionResult ItemSpec(int id, int dep, int cat, int tab)
{
// implementation that uses all four values
}
...
}
This is of course just one of the actions in it. There may be others as well. Most likely the Index one that's generated by default and is also used by default routing...
My original problem is I am looking what is the best practise to do versioning in Restful API. Not much people talk about this, dont have a good answer or I can't found exactly the solution at this moment.
(1) At first I am thinking to use Tag or Branch for each version http://domain.com/API/{version}. So if new API released, I Tag it, export and publish into the respective URL but seems hard to mix the different revision of source in one web application.
(2) Then I am thinking to use this way, one controller for one version:
(Just like this question Versioning of REST API Built With ASP.NET MVC 3 - Best Practices)
http://domain.com/API/1.0/{AnAction} => will go to APIV1Controller.{AnAction}
http://domain.com/API/2.0/{AnAction} => will go to APIV2Controller.{AnAction}
but it need to write a route for each version.
(3) Third way I get the idea from PayPal API which is the version is not in the URL but in the POST parameter. So the URL fixed to http://domain.com/API/ but user must specify the Version parameter to have "1.0" or "2.0".
The solution for this: The (2) is ok for me, and currently I use this way but I want to mixed the (2) and (3) so I have a APIController which only have one Index action to check this Version parameter and transfer the request to the respective controller and action either APIV1Controller.{AnAction} or APIV2Controller.{AnAction}.
After Googling and Stackoverflowing about how to transfer, invoke or call another controller and action without redirection. Seems there is no good answer and good practise. Someone answer .NET MVC Call method on different controller by simply creating new instance of the controller. Suddenly I got the idea how about to reroute!
The question:
Is it possible to reroute the the other controller and action from another action without redirection and how to do that?
Or a specific question, when user request http://domain.com/API/{AnAction} with Version="2.0", how can I reroute from APIController.Index to APIV2Controller.{AnAction}?
I am not using IoC.
This can be done via routing constraints. Firstly implement IRouteConstraint:
public class RequestParameterConstraint : IRouteConstraint
{
public string ParameterName { get; private set; }
public string ParameterValue { get; private set; }
public RequestParameterConstraint(string parameter, string value)
{
ParameterName = parameter;
ParameterValue = value;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
RouteValueDictionary values, RouteDirection routeDirection)
{
var value = httpContext.Request[ParameterName] ?? "";
return value.Equals(ParameterValue);
}
}
Then register routes:
routes.MapRoute(
"Version10",
"API/{action}/{id}",
new { controller = "APIV1", action = "Index", id = UrlParameter.Optional },
new { header = new RequestParameterConstraint("Version", "1.0") }
);
routes.MapRoute(
"Version20",
"API/{action}/{id}",
new { controller = "APIV2", action = "Index", id = UrlParameter.Optional },
new { header = new RequestParameterConstraint("Version", "2.0") }
);
That's all. This will do the trick.
http://coderjournal.com/2010/09/simple-rest-api-versioning-using-mef-and-mvc/
This seems to do exactly what you want, but it takes a very different approach by using a completely different set of controllers, rather than rerouting to them.
Hope this helps.
I’m working out the concepts for a new project where I need to support for multilingual URL’s. Ideally all URL’s need to be in the native language of the user. So we don’t want to use domain.com/en/contact and domain.com/es/contact but we like domain.com/contact and domain.com/contactar (contactar is Spanish for contact). Internally both should be routed to the same ContactController class.
This could be handled by adding multiple static routes to Global.asax.cs for each language but we’d like to make this very dynamic and would like the user of the system to be able to change the translation of the URL’s through the content management system. So we need some kind of dynamic mapping from URL’s to controllers and actions.
By looking at the source code of MVC3 I figured out that the ProcessRequestInit method of MvcHandler is responsible for determining which controller to create. It simply looks in the RouteData to get the name of the controller. One way to override the default MVC routing would be to create a simple default route that uses a custom RouteHandler. This RouteHandler forces MVC to use my own custom subclassed version of MvcHandler that overrides the ProcessRequestInit method. This overridden method insert my own dynamically found controller and action into the RouteData before calling back to the original ProcessRequestInit.
I’ve tried this:
Global.asax.cs
routes.Add(
new Route("{*url}", new MultilingualRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Default", action = "Default" })
}
);
MultilingualRouteHandler.cs
public class MultilingualRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MultilingualMVCHandler(requestContext);
}
}
MultilingualMvcHandler.cs
public class MultilingualMVCHandler : MvcHandler
{
public MultilingualMVCHandler(RequestContext context) : base(context)
{
}
protected override void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
{
if (RequestContext.RouteData.Values.ContainsKey("controller"))
{
RequestContext.RouteData.Values.Remove("controller");
}
if (RequestContext.RouteData.Values.ContainsKey("action"))
{
RequestContext.RouteData.Values.Remove("action");
}
RequestContext.RouteData.Values.Add("controller", "Product");
RequestContext.RouteData.Values.Add("action", "Index");
base.ProcessRequestInit(httpContext, out controller, out factory);
}
}
In this handler I hardcoded the controller and action for testing purposes to some fixed values but it’s not difficult to make this dynamic. It works but the only problem is that I had to modify the source code of ASP.NET MVC3 to get it working. The problem is that the ProcessRequestInit method of MvcHandler is private and thus cannot be overridden. I’ve modified the source code and changed it to protected virtual which allows me to override it.
This is all great but possibly not the best solution. It’s cumbersome that I would always need to distribute my own version of System.Web.Mvc.dll. It would be much better that it would work with the RTM version.
Am I missing any other possibilities of hooking into ASP.NET MVC that would allow me to dynamically determine the controller and action to launch, depending on the URL? One other way I thought of is to build the RouteCollection dynamically on *Application_Start* but I think that will make it more difficult to change it on the fly.
I would appreciate any tips of hooks that I’ve not yet found.
This is fairly old now, nut just in case anyone else is looking for something similar...
Unless I'm completely misunderstanding what you want to do, it's pretty simple really.
Step 1: Add a new route to global.ascx.cs containing a reference to your personal routing engine
routes.Add(new MyProject.Routing.ContentRoutingEngine());
Make sure that it is in the right place in the list of routes so that other routing engines can catch stuff before it if required, or continue the route search if your engine doesn't handle a particular route. I put it after the ignores, but before the MVC default routes.
Step 2: Create the Content Routing Engine, making sure that it inherites from System.Web.Routing.RouteBase abstract class, and overrides the GetRouteData and GetVirtualPath methods as required e.g.
public class ContentRoutingEngine : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeHandler = new MvcRouteHandler();
var currentRoute = new Route("{controller}/{action}", routeHandler);
var routeData = new RouteData(currentRoute, routeHandler);
// set your values dynamically here
routeData.Values["controller"] = "Home" ;
// or
routeData.Values.Add("action", "Index");
// return the route, or null to have it passed to the next routing engine in the list
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//implement this to return url's for routes, or null to just pass it on
return null;
}
}
and that should do it. You can change routes as dynamically as you wish within your engine, and no changes to MVC source required. Let the standard MVC RouteHandler actually invoke the controller.
Postscript: Obviously the code above is not production standard - it's written to make it as obvious as possible what's going on.
If you are allowing modification of urls through your CMS, then you will have to keep all old versions of the urls so that you can 301 redirect to the new ones.
The best bet for this will be to put the url tokens eg "contactar" in the db along with its corresponding controller.
query that, and create your routes out of that.
create a route that will handle the 301s
I think that most elegant solution would be using some action filter combined with custom ActionInvoker. That way, you could invoke an action that has specific filters applied. Something like ActionName attribute, only capable to accept multiple values (names).
Edit: Take a look at ActionMethodSelectorAttribute, meybe you don't need a custom ActionInvoker after all.
Say I have the following route:
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" });
Lets also say that my controller has the following methods: Index(Int32 id) and Edit(Int32 id).
So /MyController/Index/1 is a valid URL for that route. So is /MyController/Edit/1
However, if a URL is received that correctly maps to my controller but not to an existing action, how do I define a "Default Action" to execute instead of letting the MVC framework throw up an error screen?
Basically I'd like the URLs /MyController/Preview/1 and /MyController/Whatever/1 to execute an action that I specify ahead of time when the {action} token can't be mapped to an existing action on my controller.
I see that the MvcContrib project on Codeplex has an attribute that enables this for use with the ConventionController, but I'd like to keep this with pure MS ASP.NET MVC for now.
I also see that Fredrik mentions a [ControllerAction(DefaultAction = true)] attribute, but I can't find mention of it anywhere except his blog (and my app won't compile when I try it in my controller).
You can do the following for now.
protected override void HandleUnknownAction(string actionName) {
//your code here.
}
Another approach is that you put a constraint on the default route so it only matches methods you know exist on the controller. Then you could have another route like so:
routes.MapRoute("default-action", "{controller}/{actionName}/{id}", new {action="DefaultAction"});
Which maps to
public ActionResult DefaultAction(string actionName, string id) {
//handle default action
}
This gets you the result you're looking for.
Farooq Kaiser did an article on CodeProject on this topic which I found useful:
Handling Unknown Actions in ASP.NET MVC
I particularly like the trick of creating "view only" pages (obviously error handling code should be added):
protected override void HandleUnknownAction(string actionName)
{
this.View(actionName).ExecuteResult(ControllerContext);
}