How to access UrlHelper.Action or similar from within Global asax - c#

I am trying to prepare a 301 redirect for a typo I made 'recieved'
I am struggling to find a way of getting the url from the action and controller names.
I am aware of UrlHelper.Action but it does not exist within Global.asax. How do I gain access to this method?:
// Add permanent redirection for retired pages (Application_BeginRequest())
if (HttpContext.Current.Request.Url.LocalPath.ToLower().StartsWith("/blah/listrecieved"))
{
HttpContext.Current.Response.RedirectPermanent(/*Need url generated from action and controller*/);
}
Alternatively I have created a route, if that's how I should be getting the string, this is also fine but I am unsure of how:
routes.MapRoute(
name: "blah-list-received",
url: "blah/list-received",
defaults: new { controller = "Blah", action = "ListReceived" }
);
for example, it might look like this:
// Add permanent redirection for retired pages
if (HttpContext.Current.Request.Url.LocalPath.ToLower().StartsWith("/blah/listrecieved"))
{
HttpContext.Current.Response.RedirectPermanent(routes.GetUrl( "blah-list-received" ) );
}

You need to construct the UrlHelper yourself:
var url = new UrlHelper(HttpContext.Current.Request.RequestContext, RouteTable.Routes)
.Action("YourAction",
"YourController",
new { paramName = paramValue });
See MSDN

Related

If Route doesn't exist -> go to other route using its default action?

So I'm having a little problem here with routing.
There are two parts to this web application:
  1. Brochure / Display Website
  2. Internal Site / Client Application
We wanted a way to release changes for the brochure without having to do a whole release of said Web application.
Visiting existing named views will take the user to a brochure page, however if it doesn't exist, it will act like they are a client and will redirect them to their company's login screen.
Global.asax:
//if view doesnt exist then url is a client and should be redirected
routes.MapRoute(
name: "Brochure",
url: "{id}",
defaults: new { controller = "brochure", action = "Brochure", id = "Index" },
namespaces: new[] { "Web.Areas.Brochure.Controllers" }
);
//This is home page
routes.MapRoute(
name: "HomeDefault",
url: "{client}/{action}",
defaults: new { controller = "home", action = "index" },
namespaces: new string[] { "Web.Controllers" }
);
Controller:
/// <summary> Check if the view exists in our brochure list </summary>
private bool ViewExists(string name) {
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);
return (result.View != null);
}
/// <summary> Generic action result routing for all pages.
/// If the view doesn't exist in the brochure area, then redirect to interal web
/// This way, even when we add new pages to the brochure, there is no need to re-compile & release the whole Web project. </summary>
public ActionResult Brochure(string id) {
if (ViewExists(id)) {
return View(id);
}
return RedirectToRoute("HomeDefault", new { client = id });
}
This code works fine up until we log in and go to the landing page. It seems to keep the Brochure action in the route and doesn't want to go to the subsequent controller which results in a 500 error.
e.g. 'domain/client/Brochure' when it needs to be: 'domain/client/Index'
Things tried but not worked:
Changing RedirectToRoute() to a RedirectToAction() - this results in a
finite loop of going back to the ActionResult Brochure(). So
changing controllers through that didn't work.
Create an ActionResult called Brochure() inside the 'HomeController'. It
doesn't even get hit.
Passed in namespaces for RedirectToRoute() as an attribute. I knew this would probably not work, but it was worth a try.
So the question is:
How can I get the route to act properly?
If you can restrict id to some subset of all values you can add that constraints to route (i.e. numbers only) to let default handle the rest.
routes.MapRoute(
name: "Brochure",
url: "{id}",
defaults: new { controller = "brochure", action = "Brochure", id = "Index" },
namespaces: new[] { "Web.Areas.Brochure.Controllers" }
constraints : new { category = #"\d+"}
);
If you can't statically determine restrictions - automatically redirecting in your BrochureController similar to your current code would work. The only problem with sample in the question is it hits the same route again and goes into infinite redirect loop - redirect to Url that does not match first rule:
// may need to remove defaults from second route
return RedirectToRoute("HomeDefault", new { client = id, action = "index" });
If standard constraints do not work and you must keep single segment in url - use custom constraints - implement IRouteConstraint and use it in first route. See Creating custom constraints.
There are several issues with your configuration. I can explain what is wrong with it, but I am not sure I can set you on the right track because you didn't provide the all of the URLs (at least not all of them from what I can tell).
Issues
Your Brouchure route, which has 1 optional URL segment named {id}, will match any URL that is 0 or 1 segments (such as / and /client). The fact that it matches your home page (and you have another route that is named HomeDefault that will never be given the chance to match the home page) leads me to believe this wasn't intended. You can make the {id} value required by removing the default value id = "Index".
The Brouchure route has a namespace that indicates it is probably in an Area. To properly register the area, you have to make the last line of that route ).DataTokens["area"] = "Brochure"; or alternatively put it into the /Areas/Brouchure/AreaRegistration.cs file, which will do that for you.
The only way to get to the HomeDefault route is to supply a 2 segment URL (such as /client/Index, which will take you to the Index method on the HomeController). The example URLs you have provided have 3 segments. Neither of the routes you have provided will match a URL with 3 segments, so if these URLs are not getting 404 errors they are obviously matching a route that you haven't provided in your question. In other words, you are looking for the problem in the wrong place.
If you provide your entire route configuration including all Area routes and AttributeRouting routes (including the line that registers them), as well as a complete description of what URL should go to what action method, I am sure you will get more helpful answers.
So the question is:
How can I get the route to act properly?
Unknown. Until you describe what properly is.
Related: Why map special routes first before common routes in asp.net mvc?
Two ways I could have solved this issue:
Way 1
I reviewed the redirect and just passed in an action in order to get a route that has 2 segments in the url. i.e. client/Index. The Index action now handles logins - going past a custom controller.
public class HomeController : CustomController
public ActionResult Brochure(string id, string action) {
if (ViewExists(id)) {
return View(id);
}
return RedirectToAction("Index", "Home", new { client = id, action = "Index" });
}
Way 2
(from #Alexei_Levenkov)
Create a custom Route constraint so the route will be ignored if the view cannot be found.
namespace Web.Contraints {
public class BrochureConstraint : IRouteConstraint {
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
//Create our 'fake' controllerContext as we cannot access ControllerContext here
HttpContextWrapper context = new HttpContextWrapper(HttpContext.Current);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "brochure");
ControllerContext controllerContext = new ControllerContext(new RequestContext(context, routeData), new BrochureController());
//Check if our view exists in the folder -> if so the route is valid - return true
ViewEngineResult result = ViewEngines.Engines.FindView(controllerContext, "~/Areas/Brochure/Views/Brochure/" + values["id"] + ".cshtml", null);
return result.View != null;
}
}
}
namespace Web {
public class MvcApplication : System.Web.HttpApplication {
//If view doesnt exist then url is a client so use the 'HomeDefault' route below
routes.MapRoute(
name: "Brochure",
url: "{id}",
defaults: new { controller = "brochure", action = "Brochure", id = "Index" },
namespaces: new[] { "Web.Areas.Brochure.Controllers" },
constraints: new { isBrochure = new BrochureConstraint() }
);
//This is home page for client
routes.MapRoute(
name: "HomeDefault",
url: "{client}/{action}",
defaults: new { controller = "home", action = "index" },
namespaces: new string[] { "Web.Controllers" }
);
}
}
I hope this helps someone else out there.

C# MVC url parameter not being read

Very new to ASP.net MVC and C# in general. Experience with PHP/NodeJS mostly, a little Java.
I have a method in a controller like so:
public ActionResult ImageProcess(string fileName){
string url = "http://myurl.com/images/" + fileName + ".jpg";
//Code to stream the file
}
And when I navigate to it as "http://myurl.com/Home/ImageProcess/12345" I get thrown a 404 error by the process as it's trying to fetch the file.
If I hard-code it like so...
public ActionResult ImageProcess(string fileName){
string url = "http://myurl.com/images/12345.jpg";
//Code to stream the file
}
...It works just fine, returns my processed image as expected.
Why is this happening?
If you're using the default routes provided for ASP.NET MVC, the fix is simple: change fileName to id.
Example:
public ActionResult ImageProcess(string id) {
string url = "http://myurl.com/images/" + id + ".jpg";
}
In the file RouteConfig.cs you should see something like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "YourProject.Controllers" }
);
This is the configuration that tells the framework how to interpret URL strings and map them to method calls. The parameters for these method calls need to be named the same as in the route.
If you want the parameter to be named fileName, just rename {id} to {fileName} in RouteConfig.cs, or create a new route with a new name and defaults above the default route. But, if that's all you're doing, you might as well stick with the default route and name the parameter id in your action.
Your other option would be to use a query parameter, which would not require any route or variable changes:
link text
Look here for a nice tutorial on routing.
Either change the routing value as #johnnyRose already suggested or change the url to a get parameter, that will let the model binding find the fileName attribute. Like this:
http://myurl.com/Home/ImageProcess?fileName=12345

Manually pass a url through the modelbinder to obtain the RouteData parameters

I have a complex ASP.NET MVC routing scenario and I want to be able to parse a URL that I pull from the 'Referrer' request header using the existing routes.
I have incoming requests that look like this:
http://hostname/{scope}/{controller}/{action}
With corresponding route mapping:
routes.MapRoute(
name: "scoped",
url: "{scope}/{controller}/{action}/{id}",
defaults: new { controller = "Equipment", action = "Index", id = UrlParameter.Optional, scope = "shared" }
);
In the OnActionExecuting method of the base class of my controllers I pull the resulting scope from the RouteData:
var scope= (filterContext.RouteData.Values["scope"] as string).ToLower();
I then use the scope to construct some filters for my database queries. It all worked perfectly fine until I moved all my Json-returning methods to a separate set of WebApi2 controllers. I now also have a route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
All ajax requests are now made to the api controllers, which means that I do not have the scope value available. I want to solve this by using the 'Referrer' URL from the request header, which is is usually a URL that does include the scope.
What I would like to do is something like this when the ApiController initializes:
public void PullCurrentScopeDomainFromRequestHeader(System.Net.Http.Headers.HttpRequestHeaders headers) {
var refererUrl = headers.GetValues("Referer").First();
//do some magic to get the scope
}
The difficulty is that the scope can also have a default value ("shared"), in case a url like "http://hostname/controller/action" get's passed in. The best (and DRYest) way to get the scope from any URL, would be by somehow using the "scoped" route that I mapped in the routing config to parse the URL somehow. I just have no idea how to do that. Can anyone help?
You just need to build up a fake HTTP context based on your URL and then use the static RouteTable to parse the URL into a RouteValueDictionary.
// Create a fake HttpContext using your URL
var uri = new Uri("http://hostname/controller/action", UriKind.Absolute);
var request = new HttpRequest(
filename: string.Empty,
url: uri.ToString(),
queryString: string.IsNullOrEmpty(uri.Query) ? string.Empty : uri.Query.Substring(1));
// Create a TextWriter with null stream as a backing stream
// which doesn't consume resources
using (var nullWriter = new StreamWriter(Stream.Null))
{
var response = new HttpResponse(nullWriter);
var httpContext = new HttpContext(request, response);
var fakeHttpContext = new HttpContextWrapper(httpContext);
// Use the RouteTable to parse the URL into RouteData
var routeData = RouteTable.Routes.GetRouteData(fakeHttpContext);
var values = routeData.Values;
// The values dictionary now contains the keys and values
// from the URL.
// Key | Value
//
// controller | controller
// action | action
// id | {}
}
Note that you can also use a specific route from the RouteTable by specifying its name.
var routeData = RouteTable.Routes["scoped"].GetRouteData(fakeHttpContext);

ASP.NET MVC - create custom route without a fallback

I'm creating an MVC project and I have the following link that leads to a list of websites for the logged in user:
http://[server]/Members/Websites
now I would like to add a new custom route under the websites part of the user that will lead to a service connection page:
http://[server]/Members/Websites/Connect-to-service/999
where "999" is the id of the website that is being connected.
I've defined the following route for that manned (placing it above the default route):
routes.MapRoute(
name: "myRoute",
url: "Members/Websites/Connect-to-service/{id}",
defaults: new { controller = "Members", action = "Connect_To_Service" }
);
now, if I enter the url "http://[server]/Members/Websites/Connect-to-service/999" it loads my page fine.
if I enter the url "http://[server]/Members/Websites/Connect-to-service/" it loads the websites page.
what I would like to have is when loading "http://[server]/Members/Websites/Connect-to-service/" it will either redirect to "http://[server]/Members/Websites/" (in the browser url), or throw a page not found error.
can this be done?
thanks
You could configure this in the Connect_To_Service action:
public ActionResult Connect_To_Service(int? id = null)
{
if (!id.HasValue)
{
// Either:
return RedirectToAction("Websites", "Members");
// Or:
return HttpNotFound();
}
// An id is provided. Process..
}
You might need to change your route by adding a default value for id:
defaults: new { controller = "Members", action = "Connect_To_Service", id = UrlParameter.Optional }

ASP.NET MVC 2 Routing with additional consistent parameters (not just controller & action)

Currently, I have URLs that look like this:
http://www.example.com/user/create
http://www.example.com/user/edit/1
But now, I have to support multiple organizations and their users. I need to have something like this:
http://www.example.com/org-name/user/create
http://www.example.com/org-name/user/edit/1
I was having trouble getting the routes to work just perfectly, so I had to add a token to the beginning of the organization name so that routing wouldn't confuse it with a controller/action pair. Not a huge deal but my URLs look like this now:
http://www.example.com/o/org-name/user/create
http://www.example.com/o/org-name/user/edit/1
That's fine. I can live with that.
Here's where I'm running into trouble:
When I generate URLs once I have an organization selected, it's not persisting the organization name. So when I'm here:
http://www.example.com/o/org-name
...and I use Url.Action("User", "Create") to generate a URL, it outputs:
/user/create
...rather than what I want:
/o/org-name/user/create
This is what my routes look like (in order):
routes.MapRouteLowercase(
"DefaultOrganization",
"{token}/{organization}/{controller}/{action}/{id}",
new { id = UrlParameter.Optional },
new { token = "o" }
);
routes.MapRouteLowercase(
"OrganizationDashboard",
"{token}/{organization}/{controller}",
new { controller = "Organization", action = "Dashboard" },
new { token = "o" }
);
routes.MapRouteLowercase(
"DefaultSansOrganization",
"{controller}/{action}/{id}",
new { controller = "Core", action="Dashboard", id = UrlParameter.Optional }
);
It's similar to this question ASP.NET MVC Custom Routing Long Custom Route not Clicking in my Head.
I have a feeling this is going to end up being obvious but it's Friday and it's not happening right now.
EDIT:
Womp's suggested worked but would this be the best way to automate this?
public static string ActionPrepend(this UrlHelper helper, string actionName, string controllerName)
{
string currentUrl = helper.RequestContext.RouteData.Values["url"] as string;
string actionUrl = string.Empty;
if (currentUrl != null)
{
Uri url = new Uri(currentUrl);
if (url.Segments.Length > 2 && url.Segments[1] == "o/")
actionUrl = string.Format("{0}{1}{2}{3}", url.Segments[0], url.Segments[1], url.Segments[2],
helper.Action(actionName, controllerName));
}
if(string.IsNullOrEmpty(actionUrl))
actionUrl = helper.Action(actionName, controllerName);
return actionUrl;
}
EDIT:
Fixed my routes to work rather than hacking it together. The final solution didn't need the stupid {token} in the URL. Maybe this'll help someone else:
routes.MapRouteLowercase(
"Organization",
"{organization}/{controller}/{action}/{id}",
new { controller = "Organization", action = "Dashboard", id = UrlParameter.Optional },
new { organization = #"^(?!User|Account|Report).*$" }
);
routes.MapRouteLowercase(
"Default",
"{controller}/{action}/{id}",
new { controller = "Core", action = "Dashboard", id = UrlParameter.Optional }
);
Url.Action uses route values to generate the actual URL's by querying the virtual path provider and attempting to match the most specific route. In the form that you are using, you are supplying values for the controller and the action, which is as deep as most simple websites go, hence the convenient form of the method. When Url.Action queries the routing system, it only has a "controller" and an "action" segment to match.
If you give the method the rest of the routing information it needs, it will properly match the route that you desire, and will return the correct URL. Try this:
Url.Action("User", "Create", new { token = "o", organization = "organization" })

Categories