Routing: RedirectToAction(string action) - asp.net-mvc

This question is about the latest DNN platform release: DNN 8.0.1.
I am trying to redirect to an action in my controller-classes, but I am not able to do it.
For example the following check:
// Check if user is voting on his own item
if (item.CreatedByUserId == User.UserID)
return RedirectToAction("Index");
This is the error I receive when the controller tries to return an action.
DotNetNuke.Services.Exceptions.ModuleLoadException: No route in the route table matches the supplied values. ---> System.InvalidOperationException: No route in the route table matches the supplied values.
I don't know how routing is working in DNN (MVC) and I couldn't find anything helpful about is.
Thank you!

DNN's routing is different from MVCs because it has to untangle different modules. You could create a method in your controller to override the default method in this way:
public new ActionResult RedirectToAction(string actionName, string controllerName, object routeValues)
{
var routeVals = TypeHelper.ObjectToDictionary(routeValues);
routeVals["controller"] = controllerName;
routeVals["action"] = actionName;
return Redirect(ModuleRoutingProvider.Instance().GenerateUrl(routeVals, ModuleContext));
}

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?

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.

Replace character in url before routing in ASP.NET MVC

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.

Generate a URL From Route Data ONLY

I'm trying to do something simple in ASP.NET MVC:
RouteValuesDictionary routeValues = GetMyRouteData();
var url = new UrlHelper(Html.ViewContext.RequestContext);
return url.RouteUrl(routeValues);
The problem is that no matter what I do, the url includes route data from the current request context. I want to generate a URL based on ONLY the route values from GetMyRouteData().
Thanks
The problem is that no matter what I do, the url includes route data
from the current request context
That's by design. You have to explicitly set the route values that were present in the original request and that you don't want in the resulting url to null:
var routeValues = GetMyRouteData();
// remove values that you want to exclude from the resulting url
// by setting their values to null
routeValues["id"] = null;
var url = new UrlHelper(Html.ViewContext.RequestContext);
return url.RouteUrl(routeValues);
This is not specific to ASP.NET MVC, but due to ASP.NET Routing's route resolution. The entry point to this is RouteCollection.GetVirtualPath, which has two signatures.
The first takes a RequestContext and a RouteValueDictionary. This is used for default route resolution, which relies on pattern matching to find a route. The route search incorporates all tokens from RequestContext as well as RouteValueDictionary; in other words, the two sets of route tokens are combined to form the basis for the route search. A special case exists whereby null parameters in the RouteValueDictionary remove that parameter from search. However, if such a null-valued parameter has a value in the RequestContext, that value will still appear in the generated URL as a query string value.
The other signature additionally accepts a route name. It is a little strange because it alters both the route resolution as well as the query string creation. Routes are found, obviously, using name resolution. Given the named route is found, only tokens for those parameters specified in route's URL pattern will appear in the generated URL.
Why is it this way? It's ASP.NET MVC's interpretation of Ruby on Rails' parameter-handling convention.
So default route resolution and "fallback" token resolution are comingled. If you don't want tokens to fallback to the RequestContext, (and you still want to use ASP.NET Routing) you have to use named route resolution.
This might help clarify. Use the source, Luke!
The RouteUrl helper calls into this static method to generate the URL:
public static string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues) {
if (routeCollection == null) {
throw new ArgumentNullException("routeCollection");
}
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);
VirtualPathData vpd = routeCollection.GetVirtualPathForArea(requestContext, routeName, mergedRouteValues);
if (vpd == null) {
return null;
}
string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath);
return modifiedUrl;
}
Note the line:
RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);
This is merging in the current requestcontext values under the covers. So, you could make your own helper that just calls into this static method and passes empty collections to avoid getting the current route context values being merged in. Experiment and debug into the MVC code and you should be able to see which values you need to nuke.

Value cannot be null. Parameter name: String in asp.net mvc C#

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...

How do I set a "Default Action" for my Controller that will be called when no other action matches?

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);
}

Resources