I have an old MVC3 site where I was able to get the area of a route by using the following code:
object oArea;
RouteData.DataTokens.TryGetValue("area", out aArea);
I am creating a new MVC5 application and have started to use attribute based routing as follows:
[RouteArea("Area")]
[RoutePrefix("Test")]
public TestController
{
public ActionResult Index()
{
return View("Index");
}
}
Unfortunately, it appears that when you use attribute based routing then the RouteData.DataTokens collection is empty. The area information appears buried under the RouteData in "MS_DirectRouteMatches", so you could get the data as follows:
RouteData.Values["MS_DirectRouteMatches"])[0].DataTokens.TryGetValue("area", out oArea);
However, I was wondering if there is an easier, safer or better way to get the area data in MVC5. The area name is actually the sub-tool name within a larger application, which is used for some logic in the base controller initialization.
The only "safe" way is to first check for the existence of the MS_DirectRouteMatches and only probe for the area if it exists, falling back to the original RouteData object if it does not.
string area;
RouteData routeData = HttpContext.Request.RequestContext.RouteData;
if (routeData != null)
{
if (routeData.Values.ContainsKey("MS_DirectRouteMatches"))
{
routeData = ((IEnumerable<RouteData>)routeData.Values["MS_DirectRouteMatches"]).First();
}
routeData.DataTokens.TryGetValue("area", out area);
}
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.
All,
I have an MVC _Layout.cshtml that calls:
#Html.Action("GetActionStrip", "Vehicles")
I also have a Controller that loads some views.
My Issue is that I have a controller action called GetVehicleDetails which gets a vehicle by ID.
The Action on:
#Html.Action("GetActionStrip", "Vehicles")
requires that GetVehicleDetails loads first as it need to put a vehicle id into session.
This is not working as:
#Html.Action("GetActionStrip", "Vehicles")
Loads before GetVehicleDetails.
#Html.Action("GetActionStrip", "Vehicles")
Needs to be on multiple views, that's why I put it in the _Layout file.
I can get it to work by putting:
#Html.Action("GetActionStrip", "Vehicles")
On every view I need it on and then they load in the correct order. ie.. the controller action GetVehicleDetails sets the vehicle id into session and then:
#Html.Action("GetActionStrip", "Vehicles")
Reads the session value.
Has anyone got any idea if I can do it the way I want or will I have to put my #Html.Action on every view which kind of breaks the DRY principle.
thanks
RuSs
Paul, I tried to write you a comment but the character limit killed me. Here is my comment:
Paul,
Thanks for the message. I understand what you have written but before I continue and try to implement something like this I just want to be sure you understand, fully, the scenario.
Will YOUR scenario cater for the fact that the code that needs the session value is called from an #Html.Action in my _Layout (master page so to speak) whereas I need my controller get action to receive a parameter and set this parameter into session.
From what I understand, _Layouts (master pages) load first so my #Html.Action would run and look for the session value. But, as this code is in a _Layout, it would run first and hence the GET on my controller has not yet set the session from the actions passed in parameter.
Note: my _Layout doesnt have it's own controller (not sure if this matters)
Thanks
RuSs
Something about the design is fundamentally incorrect. You shouldn't have different components being tightly coupled like this. The order shouldn't matter for which one comes first.
Here is how I might do what you're looking for. I'd create a model bound class that you can receive in your controller actions where you need the session value. The model will pull the session value from the database or wherever if it hasn't been set yet otherwise it uses the session value. Now order doesn't matter. Better yet you could make MySessionObject an interface and then you can mock it out in your test cases.
public interface IMySessionObject
{
int GetValueX();
}
public class MySessionObject : IModelBinder, IMySessionObject
{
private HttpContextBase _httpContext;
private MySessionObject(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public int GetValueX()
{
if (_httpContext.Session["x"] == null)
{
_httpContext.Session["x"] = 54; // Get the value here.
}
return (int)_httpContext.Session["x"];
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var context = controllerContext.HttpContext;
var obj = new MySessionObject(context);
return obj;
}
}
public class HomeController : Controller
{
public ActionResult Index(IMySessionObject obj)
{
ViewBag.X = obj.GetValueX();
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult About(IMySessionObject obj)
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact(IMySessionObject obj)
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Thanks Paul. I think your answer is correct without Sitecore CMS being part if the scenario. Ill vote you up but still doesn't fix my issue. Here is my other post which explains it better.
I don't expect you use Sitecore. https://stackoverflow.com/questions/14867915/order-of-loading-layout-and-url-driven-action-is-opposite-to-a-standard-non-sit ill try to find a way for Sitecore NOT to declaratively load my _layout before my MVC code runs.
This is what I'm trying to achieve:
I want to extend the HandleErrorAttribute so I can redirect to an appropriate error page. I had a look at the implementation of HandleErrorAttribute and noticed that essentially it just returns a ViewResult, that points to a view, that can be defined per Exception type.
I'd like to use the same system as HandleErrorAttribute, except:
I'd like to define a Controller for Error pages
I want to maintain the Exception as model (HandleErrorInfo)
I could obviously use a RedirectToRouteResult, but I can't pass through the Exception as model, since it's an actual Response.Redirect. And I'd like to avoid having to cache the model server side.
For now I'm just overwriting the ViewResult and manually setting controller. But that still just returns the view and doesn't actually execute the controller.
public class ErrorViewResult : ViewResult
{
public ControllerBase Controller { get; set; }
public string ControllerName { get; set; }
protected override ViewEngineResult FindView(ControllerContext context)
{
context.Controller = Controller;
context.RouteData.Values["controller"] = ControllerName;
return base.FindView(context);
}
}
I have to somehow return a result, that restarts the whole pipeline starting with the Controller.
Any ideas?
Thanks!
Be careful with TempData functionality, it will store your values only till the next request, and if in between these requests you will do any others or if you use mvc to handle client resources (like dynamically combined css and js files) then you will loose your data.
If you want to start controller manually (with all nested actions) then look at that:
RouteData data = new RouteData();
data.Values.Add("controller", "error");
data.Values.Add("action", "Handle500");
data.Values.Add("area", "");
data.Values.Add("exception", sb.ToString());
var controller = new MTool.BusinessLogic.Controllers.ErrorController();
controller.ControllerContext = new ControllerContext([HttpContextInstance], data, controller);
controller.ActionInvoker.InvokeAction(controller.ControllerContext, "Handle500");
Why not use TempData?
TempData allows you to store data that can be read on the very next request. If they refresh the page after being redirected it will be gone.
TempData["exception"] = exception;
return RedirectToAction("Index");
Now in the get you have access to TempData["exception"] for the first GET only, then it is gone. Sounds like what you need.
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...
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.