This must have been asked before, but after reading here, here, here and here I can't extrapolate the relevant parts to make it work. I am revamping an old web forms site into MVC, and want to catch particular incoming HTTP requests so that I can issue a RedirectPermanent (to protect our Google rankings and avoid users leaving due to 404's).
Rather than intercept all incoming requests, or parse for some id value, I need to intercept all requests that end in (or contain) the .aspx file extension, e.g.
www.sample.com/default.aspx
www.sample.com/somedir/file.aspx
www.sample.com/somedir/file.aspx?foo=bar
Requests to the MVC routes should be ignored (just processed as normal).
Here's what I have so far, except the ASPXFiles route is never being hit.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// never generates a match
routes.MapRoute(
name: "ASPXFiles",
url: "*.aspx",
defaults: new { controller = "ASPXFiles", action = "Index" }
);
// Used to process all other requests (works fine)
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
Is this type of route possible to set up in MVC?
I am showing the right way to make a 301 redirect in MVC, since not all browsers respond to 301 redirect requests properly, and you need to give the user an option to continue rather than the default "Object Moved" page that is generated by ASP.NET.
RedirectAspxPermanentRoute
We build a custom RouteBase subclass that detects when a URL ends with .aspx and routes to our SystemController to setup the 301 redirect. It requires you to pass in a map of URL (the URL to match) to route values (which are used to generate the MVC URL).
public class RedirectAspxPermanentRoute : RouteBase
{
private readonly IDictionary<string, object> urlMap;
public RedirectAspxPermanentRoute(IDictionary<string, object> urlMap)
{
this.urlMap = urlMap ?? throw new ArgumentNullException(nameof(urlMap));
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var path = httpContext.Request.Path;
if (path.EndsWith(".aspx"))
{
if (!urlMap.ContainsKey(path))
return null;
var routeValues = urlMap[path];
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values["controller"] = "System";
routeData.Values["action"] = "Status301";
routeData.DataTokens["routeValues"] = routeValues;
return routeData;
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
Note that the first check is for the .aspx extension, so the rest of the logic will be entirely skipped if the extension doesn't match. This will provide the best performance for your scenario.
SystemController
We setup the SystemController to return a view as we normally would. If the browser doesn't redirect because of the 301, the user will see the view.
using System;
using System.Net;
using System.Web;
using System.Web.Mvc;
public class SystemController : Controller
{
//
// GET: /System/Status301/
public ActionResult Status301()
{
var routeValues = this.Request.RequestContext.RouteData.DataTokens["routeValues"];
var url = this.GetAbsoluteUrl(routeValues);
Response.CacheControl = "no-cache";
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = url;
ViewBag.DestinationUrl = url;
return View();
}
private string GetAbsoluteUrl(object routeValues)
{
var urlBuilder = new UriBuilder(Request.Url.AbsoluteUri)
{
Path = Url.RouteUrl(routeValues)
};
var encodedAbsoluteUrl = urlBuilder.Uri.ToString();
return HttpUtility.UrlDecode(encodedAbsoluteUrl);
}
}
Status301.cshtml
Follow the conventions of MVC and be sure to place this in the /Views/System/ folder.
Because it is a view for your 301 response, you can make it match the theme of the rest of your site. So, if the user ends up here, it is still not a bad experience.
The view will attempt to redirect the user automatically via JavaScript and via Meta-Refresh. Both of these can be turned off in the browser, but chances are the user will make it where they are supposed to go. If not, you should tell the user:
The page has a new location.
They need to click the link if not automatically redirected.
They should update their bookmark.
#{
ViewBag.Title = "Page Moved";
}
#section MetaRefresh {
<meta http-equiv="refresh" content="5;#ViewBag.DestinationUrl" />
}
<h2 class="error">Page Moved</h2>
<p>
The page has moved. Click on the following URL if you are
not redirected automatically in 5 seconds. Be sure to update your bookmarks.
</p>
#ViewBag.DestinationUrl.
<script>
//<!--
setTimeout(function () {
window.location = "#ViewBag.DestinationUrl";
}, 5000);
//-->
</script>
Usage
First you need to add a section to your _Layout.cshtml so the Meta-refresh can be added to the head section of your page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>#ViewBag.Title - My ASP.NET MVC Application</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<!-- Add this so the view can update this section -->
#RenderSection("MetaRefresh", required: false)
<meta name="viewport" content="width=device-width" />
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
</head>
<!-- layout code omitted -->
</html>
Then add the RedirectAspxRoute to your routing configuration.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new RedirectAspxPermanentRoute(
new Dictionary<string, object>()
{
// Old URL on the left, new route values on the right.
{ #"/about-us.aspx", new { controller = "Home", action = "About" } },
{ #"/contact-us.aspx", new { controller = "Home", action = "Contact" } }
})
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Try something like this:
routes.MapRoute(
name: "ASPXFilesWithFolderPath",
url: "{folder}/{page}.aspx",
defaults: new { controller = "ASPXFiles", action = "Index", folder=UrlParameter.Optional, page = UrlParameter.Optional }
);
routes.MapRoute(
name: "ASPXFiles",
url: "{page}.aspx",
defaults: new { controller = "ASPXFiles", action = "Index", page = UrlParameter.Optional }
);
Initially I was going to suggest and HTTPHandler but aspx extension is mapped in IIS by default and therefore will not work. Here's a link to Jon Galloway's blog
Since my situation I only had a few main pages, with cockamamy rules, I found this easier.. To create an "oldaspxcontroller". So I can be sure everything mapped correctly.
//https://www.oldsite.com/topics/travel/page9.aspx
[HttpGet]
[Route("/topics/{topic}/page{oldpagenum}.aspx")]
public LocalRedirectResult TopicWithPage(string topic, string oldpagenum)
{
return LocalRedirectPermanent($"/topics/{topic}?p={oldpagenum}");
}
You may notice I still use pagenum in querystring.. I just think it looks better.. like
mysite.com/topics/travel?p=9 I like better than mysite.com/topics/travel/page/9. I am in .Net core 3.1 and it works nice, even recognizes the pattern and the pagenumber..
Related
I have a problem in my MVC project were the first time I request a page it makes a redirection to another area, but the second time I request it works correctly.
The url I use is: http://localhost:2012/pt/Documentation/Index and redirects me to http://localhost:2012/es/Services/Documentation
I've debugged the code and in both requests the url asked for is the first, and the action that respond the request is correct, but once the reply gets to the browser , the first time is recieved with a 302 code and it redirects me to this second URL.
I've checked the web.config with any redirection rule but there aren't.
The Area registration is:
routes.MapRoute(
name: "Default",
url: "{language}/{controller}/{action}/{id}",
defaults: new { language = "es", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And the controller is like:
public class DocumentationController : Controller
{
public ActionResult Index(string language)
{
return View();
}
}
The setCulture is an atribute that is used to change the translations needed in the view.
I've tried to comment the view and the layout but it still redirects without code.
Any help will be aprecciated.
****EDIT****
First request:
Second request:
The view code is:
#model string
#{
ViewBag.Title = "Resend documentation";
Layout = null;
}
I guess I don't completely understand how urls work with C# projects, in the sense that I don't know how to specify a url to go through the controller and not just return a aspx page.
Say I am trying to get to my project's Index page through a Controller named "ScholarshipController.cs". I would think to hit the Index method/action in this controller, my url would be as follows (my app's name is "TuitionAssistance" fyi):
http://localhost/TuitionAssistance/Scholarship/Index
However, running this url just returns the aspx page named "Index.aspx" located in the "Scholarship" view file without hitting the Controller. Why is this happening, and how do I get it to go through the controller so the Index page, when loaded, will have the appropriate information loaded onto it?
Sorry if this is a dumb question. Any insight would be appreciated. Thanks!
Route.config:
using System.Web.Mvc;
using System.Web.Routing;
namespace ScholarshipTuitionAssistance
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
/* Scholarship */
/* Scholarship */
//routes.MapRoute("TuitionAssistance",
// "tuition/{name}",
// new { controller = "TuitionAssistance", action = "Index", name = "" });
routes.MapRoute(
name: "TuitionAssistance",
url: "{controller}/{action}/{employee_number}",
defaults: new { controller = "Home", action = "TuitionAssistance", employee_number = UrlParameter.Optional }
);
routes.MapRoute(
name: "Scholarship",
url: "{controller}/{action}/{employee_number}",
defaults: new { controller = "Home", action = "Scholarship", employee_number = UrlParameter.Optional }
);
routes.MapRoute(
name: "Details",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Scholarship", action = "Details", id = UrlParameter.Optional }
);
}
}
}
Your route (URL) cannot match anything that actually exists on the filesystem. In your example here, you apparently have a file, [document root]\Scholarship\Index.aspx. As a result, a request for Scholarship/Index will return that file, instead of invoking the ASP.NET MVC machinery to load a controller action.
In MVC ASP.NET, think of those types of links as a way to call your methods in your controller. When that link is accessed, your controller does a bunch of junk and then returns an ActionResult (or other things). This ActionResult, for the sake of this explanation, is the markup that is written in the corresponding view file. Controller - >index() will return the view called index under views - > controller. If you want to pass information to your view, you will pass a model that has all of your information in it to the view from your index controller (return View(MyFancyModel)). The view will have a razor line at the top such as: #model The.Namespace.Wherever.my.model.is
The scaffolded controllers and views in Visual Studio for the index page specifically, only pass a list of the items in the corresponding database.
I'm lost..
For days now, i have tried to get it to work.. I made a MVC site code first with EF. then i've scaffolded controllers and API (tried ALOT thins)
my routeConfig:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Events", action = "Index", id = UrlParameter.Optional }
);
my WbApiConfig:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
What should my Controllers actions return? json? views?
my ng-app is in my /Home/Index (which uses layout, and layout has ng app on html)
and at last, my ngApp
app.config(['$routeProvider', function ($routeProvider, $locationProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl: 'Home/Index',
controller: 'mainController'
})
.when('/Home/About', {
templateUrl: '/Home/About',
controller: 'programsCtrl'
});
}]);
So.. the furthest I've got is a 404 or angular crashing chrome by loading infinitly.
And with the code above, i get the angular load more than one crash.
I just want my angular to load my views inside the ng-view and leave the layout on always..
ANY help or pointer appreciated :)
I recommend you to use UI router, You create a one controller name 'HomeController' inherited from _layout. Your index view of Home contains this div
<div ui-view></div>
You app.js file looks like, don't forget to add ui-router js in layout
var app = angular.module('app', ['ngRoute', 'ui.router']);
app.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
// For any unmatched url, redirect to /state1
$urlRouterProvider.otherwise("/Home");
// Now set up the states
$stateProvider
.state('Home', {
url: "/Home",
controller: "mainController",
templateUrl: "your html pages" // like '/script/home/templates/home.html'
}).state('About', {
url: "/Home/About",
controller: "programsCtrl",
templateUrl: "your html pages" // like '/script/home/templates/about.html'
});
}]);
The way I go about this is by setting the default for MVC to home/index and placing the <ng-view></ng-view> in the home/index view. This should be the only content in that view, remove everything else.
Now, on your _layout, make sure you have:
<html ng-app="app"> at the top of the file and ensure that you have the relevant scripts loaded ie:
#Scripts.Render("~/bundles/angularjs")
#Scripts.Render("~/bundles/app")
So that angular is available.
Keep this for #RenderBody()
<div class="body-content">
#RenderBody()
</div>
Your MVC methods return ActionResult (return View()) and your ApiController methods return JSON
I just created a new MVC 4 project, and added an EDO.NET Entity Data Model using Database First. I'm not sure exactly why, but things don't seem to be functioning correctly as they used to. I had to manually add the EF Code Generation item to generate the entity classes.
Anyway, the main problem I have is that the default routing seems to be ignored.
My route config is the default, which is as follows:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
However [LOCALHOST]/Properties/ doesn't find /Properties/Index, it merely returns a 404 Server Error in '/' Application.
The resource cannot be found.
I wouldn't put it past me to have made some silly mistake or forgotten something crucial, but I've searched StackOverflow and the interwebs for similar problems and none of the solutions are of any help. If anyone knows why, I'd be grateful for a prod in the right direction.
Requested Edits:
I have 3 Controllers:
Home - Untouched
Account - Untouched
Properties - w/ Default MVC CRUD Actions (Index, Details, Create, Edit)
It works fine when hosted on IIS but not on VS's internal debugging IIS.
#Html.ActionLink("Properties", "Index", "Properties") generates http://[localhost]:53909/Properties when run. However clicking the generated link gives me a "Server Error in '/' Application.
The resource cannot be found."
PropertiesController.cs (only Index action)
public class PropertiesController : Controller
{
private PropertyInfoEntities db = new PropertyInfoEntities();
//
// GET: /Properties/
public ActionResult Index()
{
//Mapper.CreateMap<Property, PropertiesListViewModel>()
//.ForMember(vm => vm.MainImageURL, m => m.MapFrom(u => (u.MainImageURL != null) ? u.MainImageURL : "User" + u.ID.ToString()))
// ;
//List<PropertiesListViewModel> properties =
// Mapper.Map<List<Property>, List<PropertiesListViewModel>>(db.Properties.ToList());
return View(db.Properties.Include(p => p.Currency).Include(p => p.Type).Include(p => p.Province).Include(p => p.Region).Include(p => p.SaleType).Include(p => p.Source).Include(p => p.Town).ToList());
}
}
_Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>#ViewBag.Title - My ASP.NET MVC Application</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
</head>
<body>
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">#Html.ActionLink("your logo here", "Index", "Home")</p>
</div>
<div class="float-right">
<section id="login">
#Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Properties", "Index", "Properties")</li>
<li>#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
</nav>
</div>
</div>
</header>
....
Edit 2:
Even with a specific route it is still ignored
namespace ProjectName
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Properties",
"Properties/{action}/{id}",
new { controller = "Properties", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
Cheers!
I tried changing the name of PropertiesController to Properties1Controller, and the routing worked for it completely fine. After some further digging I discovered that it's because Properties is a Windows Reserved Keyword.
Anyhow, the solution to the problem: Do not use reserved keywords as controller names.
Strangely routing for /Properties/Index works completely fine, and routing for /Properties/ works completely fine on production IIS, just not for development. This made it much harder to work out the problem but managed to get there in the end.
Thank-you all for your assistance.
In your Global.asax.cs file, have you registered the routes? For example:
RouteConfig.RegisterRoutes(RouteTable.Routes);
Visiting localhost/properties is explicitly saying that you want to invoke PropertiesController. If you mean you want that to be your default route, then you need to change your route config to the following:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Properties", action = "Index", id = UrlParameter.Optional }
);
That would allow you to invoke it directly from localhost. However, it sounds as though you are missing the .cshtml file. Have you made sure ~/Views/Properties/Index.cshtml exists?
Default route means that every [localhost]/foo/bar request is forwarded to FooController and its Bar action that must return some existing View. Probably you don't have some of them.
"defaults" parameter sets default controller and action names in case they are not specified in request (i.e. http://[localhost]/) so in your case this will search for HomeController and its Index action.
Does the namespace of the Properties controller match the "ProjectNamespace.Controllers" convention? If you copied the code from another source you may have forgotten to change the namespace of the controller class.
I have a domain "http://www.abc.com". I have deployed an ASP.net MVC4 app on this domain. I have also configured a default route in RouteConfig.cs as shown below
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "MyApp", action = "Home", id = UrlParameter.Optional }
);
The above mapping ensures that anyone attempting to visit "http://www.abc.com" is automatically shown the page for "http://www.abc.com/MyApp/Home"
Everything works as expected but the address bar in the browser shows "http://www.abc.com" instead of "http://www.abc.com/MyApp/Home". Is there any way to force the browser to show the complete URL including the controller and Action?
One option would be to set your default route to a new controller, maybe called BaseController with an action Root:
public class BaseController : Controller
{
public ActionResult Root()
{
return RedirectToAction("Home","MyApp");
}
}
and modify your RouteConfig to point to that for root requests:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Base", action = "Root", id = UrlParameter.Optional }
);
You'll need to do some kind of url rewriting. Probably the quickest way is to add a RewritePath call to your BeginRequest in Global.asax. In your case it'd be something like this:
void Application_BeginRequest(Object sender, EventArgs e)
{
string originalPath = HttpContext.Current.Request.Path.ToLower();
if (originalPath == "/") //Or whatever is equal to the blank path
Context.RewritePath("/MyApp/Home");
}
An improvement would be to dynamically pull the url from the route table for the replacement. Or you could use Microsoft URL Rewrite, but that's more complicated IMO.
Just remove the default parameters, it's been answered here:
How to force MVC to route to Home/Index instead of root?