Nasty interaction between ASP.NET MVC Routing and my login system - asp.net-mvc

I am developing an ASP.NET MVC application that has two kind of pages: (1) a login page, and (2) everything else. Even my home page displays content that requires authorized access:
public class HomeController : Controller {
[CustomAuthorize] // My custom authorization tag
public ActionResult Index() {
// ...
}
}
But now I have the following "little" problem. When I navigate to http://my-site/, the following sequence of events takes place:
Since no controller and no action were specified, the default values ("Home" and "Index", respectively) are used.
Since HomeController.Index() has the CustomAuthorizeAttribute attribute, then I get redirected to my login page.
My login page attempts to load, among other things http://my-site/Content/Site.css.
In this new request, since there is no controller called ContentController, ASP.NET processes the request as if Content and Site.css were parameters of a request to http://my-site/. Which, of course, requires authentication, and...
Is there any way to make ASP.NET MVC Routing process requests to http://my-site/Content/* or http://my-site/Scripts/* differently than other requests?
EDIT: Here is my global.asax file:
public class MvcApplication : HttpApplication {
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
private void Application_Start() {
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}

Do you have access restrictions in your web.config? If so, you shouldn't. Typically in an MVC app, you handle that entirely with attributes. I think if actual files exist on disk at the path the request won't even be routed through MVC. Given your description it seems most likely that your web.config is set up to deny access to unauthenticated users and that should be removed.

Related

MVC3 Custom routing

I developed a website with MVC and I have a little problem on how link addresses appears in the address bar.
When I open the website, I have to log on first; after I log on into the account, the home page appear, but in the browser address bar I still have
http://localhost:1413/Account/LogOn
instead of
http://localhost:1413/Home
Also, after I log out, I am redirected to the log in page, but in the address bar it appears
http://localhost:1413/Account/LogOn
I would like to be just
http://localhost:1413/Account/LogOff
My Global.asx code
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
I used this type of redirection, but the result is the same:
public ActionResult LogOn()
{
if (HttpContext.User.Identity.IsAuthenticated == true)
{
return RedirectToAction("Index", "Home");
}
return View();
}
you need to use redirect action to home page like this
RedirectToAction("Action", "Controller")
so when user has been authenticated you need to redirect the user to the particular controller.
Same goes for the when user logged off
As I can see, your problem is not the routing cause your routing is working fine. Your problem is that you work in the controller Account. That's why account always appears in you url. If you want to get this:
http://localhost:1413/Home
You must link to a controller which you called HomeController.cs
I hope you understand what i tried to explain.
Maybe this can help you more:
http://www.codeproject.com/Articles/190267/Controllers-and-Routers-in-ASP-NET-MVC-3

In ASP.NET MVC, why do I get 404 errors after Publishing my website?

I'm still new to ASP.NET MVC and I'm struggling a little with the routing.
Using the ASP.NET development server (running directly from Visual Studio), my application can find its views without any problems. The standard ASP.NET URL is used - http://localhost:1871/InterestingLink/Register
However, when I publish my site to IIS and access it via http://localhost/MyFancyApplication/InterestingLink/Register, I get a 404 error.
Any suggestions on what might be wrong?
More info...
This is what my global.asax file looks like (standard):
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
My controller is also very simple:
public class InterestingLinkController : Controller
{
public ActionResult Register()
{
return View("Register");
}
}
I figured out what was wrong. The problem was actually that IIS5 (in Windows XP) does not fire up ASP.NET when the URL does not contain a .ASPX. The easiest way to get around this is to add a '.aspx' to your controller section in global.asax. For example:
routes.MapRoute(
"Default",
"{controller}.aspx/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
Not pretty, but it will do.
Lots of things could be wrong:
Is the IIS Virtual Directory & Application set correctly?
Is the ASP.NET application being called at all? (Add some logging/breakpoiont in Application_Start and Application_BeginRequest)
Just for a start. You are going to have to apply the usual debugging approaches.
(To avoid issues like this, I rarely use the development server and just use IIS the whole time: most difficult thing is remembering to run VS elevated every time.)

How to config Asp.net Mvc for redirecting every request to configuration page?

For Asp.net Mvc project, I need to redirect every request to configuration page when user(should be admin of this website) visit this website at the first time. This operation like default login page(every request will be redirect to default login page if access denied).
After user config the configuration file, Route table will be mapped to normal controllers.
Ps. This page should helps Admin for detect error configuration and easy to deploy.
Update #1
I try to use ASP.NET MVC WebFormRouting Demo on Codeplex. But I can't redirect when user visit some existing page like "~/AccessDenied.aspx" or "~/web.config".
routes.MapWebFormRoute("RedirectToConfig", "{*anything}", "~/App_Config");
Thanks,
From your description, this appears to be an authorization concern, so I would recommend a custom Authorize attribute class (inherit from AuthorizeAttribute).
From here you can override the OnAuthorization method where you can check if the user has completed your required configuration steps and set the filterContext.Result accordingly. A basic implementation would look something like this (this assumes you have a valid /Account/Configure route):
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var user = ; // get your user object
if(user.IsConfigured == false) // example
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{
"ConfigureUserRoute",
filterContext.RouteData.Values["ConfigureUserRoute"]
},
{"controller", "Account"},
{"action", "Configure"}
});
return;
}
}
}
You can find other examples of how to create a custom AuthorizeAttribute class here on StackOverflow.
2 ideas:
Use a catch-all rule on top of your routing table and put a constraint on it that checks for the config status
Put the code for this check in Application_BeginRequest in GlobalAsax
Details for the catch-all idea:
Create a rule with url "{*path}" and put it first in your list
Create a constraint to activate this rule only in case the configuration is not done yet
Create a simple controller e.g. ConfigController with a single action that does nothing but a RedirectToUrl("config.aspx")
But the solution in Application_BeginRequest would be simpler, since the whole code to handle this in one place
Now, I can apply technique from my another question to solve this problem. By keep some value in static instance when application is starting. Please look at the following code.
partial ConfigBootstapper.cs
public class ConfigBootstapper
{
public static EnableRedirectToConfigManager = false;
}
partial ConfigModule.cs
void HttpApplication_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if (ConfigBootstapper.EnableRedirectToConfigManager)
{
app.Response.Redirect("~/App_Config");
}
}
partial Global.asax
protected void Application_Start()
{
[logic for setting ConfigBootstapper.EnableRedirectToConfigManager value]
}
PS. Don't forget to checking some condition that cause infinite-loop before redirect.

Is it possible to run ASP.NET MVC routes in different AppDomains?

I am having problems with thinking up a solution for the following. I got a blog which I recently upgraded from web forms to MVC. The blog is avalible in both swedish and english on two different domains and are running in the same web site in IIS.
The problem is that I would like language specific urls on the both sites, like this:
English: http://codeodyssey.com/archive/2009/1/15/code-odyssey-the-next-chapter
Swedish: http://codeodyssey.se/arkiv/2009/1/15/code-odyssey-nasta-kapitel
At the moment I have made this to work by registering the RouteTable on each request depending on which domain is called. My Global.asax Looks something like this (not the whole code):
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
string archiveRoute = "archive";
if (Thread.CurrentThread.CurrentUICulture.ToString() == "sv-SE")
{
archiveRoute = "arkiv";
}
routes.MapRoute(
"BlogPost",
archiveRoute+"/{year}/{month}/{day}/{slug}",
new { controller = "Blog", action = "ArchiveBySlug" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
routes.MapRoute(
"404-PageNotFound",
"{*url}",
new { controller = "Error", action = "ResourceNotFound" }
);
}
void Application_BeginRequest(object sender, EventArgs e)
{
//Check whcih domian the request is made for, and store the Culture
string currentCulture = HttpContext.Current.Request.Url.ToString().IndexOf("codeodyssey.se") != -1 ? "sv-SE" : "en-GB";
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(currentCulture);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(currentCulture);
RouteTable.Routes.Clear();
RegisterRoutes(RouteTable.Routes);
Bootstrapper.ConfigureStructureMap();
ControllerBuilder.Current.SetControllerFactory(
new CodeOdyssey.Web.Controllers.StructureMapControllerFactory()
);
}
protected void Application_Start()
{
}
This works at the moment but I know it not a great solution. I have been getting a "Item has already been added. Key in dictionary" error when stating up this app and it does not seems stable at times.
I would like to only set up my routes in the Application_Start as they should and not having to clear them on every request like I am doing now. Problem is that the request object does not exist and I have no way of knowing which of the language specific routes I should register.
Been reading about the AppDomain but could not find many examples on how to use it on a web site. I'we been thinking to star something like this:
protected void Application_Start()
{
AppDomain.CreateDomain("codeodyssey.se");
AppDomain.CreateDomain("codeodyssey.com");
}
Then registring each web sites routes in each app domain and send the requests to one of them based on the url. Can't find any examples on how to work with AppDomains in this manner.
Am I completely off track? Or is there a better solution for this?
The ASP.Net runtime manages AppDomains for you, so its probably not a good idea to create AppDomains in your code.
However, if you can, I would suggest creating multiple IIS Applications (one for http://codeodyssey.com and one for http://codeodyssey.se). Point both applications at the same directory on disk. This will give you the two AppDomains you are looking for.
Then, in your Application_Start code, you can check the domain and build routes accordingly.

Asp.Net MVC: How do I enable dashes in my urls?

I'd like to have dashes separate words in my URLs. So instead of:
/MyController/MyAction
I'd like:
/My-Controller/My-Action
Is this possible?
You can use the ActionName attribute like so:
[ActionName("My-Action")]
public ActionResult MyAction() {
return View();
}
Note that you will then need to call your View file "My-Action.cshtml" (or appropriate extension). You will also need to reference "my-action" in any Html.ActionLink methods.
There isn't such a simple solution for controllers.
Edit: Update for MVC5
Enable the routes globally:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
// routes.MapRoute...
}
Now with MVC5, Attribute Routing has been absorbed into the project. You can now use:
[Route("My-Action")]
On Action Methods.
For controllers, you can apply a RoutePrefix attribute which will be applied to all action methods in that controller:
[RoutePrefix("my-controller")]
One of the benefits of using RoutePrefix is URL parameters will also be passed down to any action methods.
[RoutePrefix("clients/{clientId:int}")]
public class ClientsController : Controller .....
Snip..
[Route("edit-client")]
public ActionResult Edit(int clientId) // will match /clients/123/edit-client
You could create a custom route handler as shown in this blog:
http://blog.didsburydesign.com/2010/02/how-to-allow-hyphens-in-urls-using-asp-net-mvc-2/
public class HyphenatedRouteHandler : MvcRouteHandler{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["controller"] = requestContext.RouteData.Values["controller"].ToString().Replace("-", "_");
requestContext.RouteData.Values["action"] = requestContext.RouteData.Values["action"].ToString().Replace("-", "_");
return base.GetHttpHandler(requestContext);
}
}
...and the new route:
routes.Add(
new Route("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Default", action = "Index", id = "" }),
new HyphenatedRouteHandler())
);
A very similar question was asked here: ASP.net MVC support for URL's with hyphens
I've developed an open source NuGet library for this problem which implicitly converts EveryMvc/Url to every-mvc/url.
Uppercase urls are problematic because cookie paths are case-sensitive, most of the internet is actually case-sensitive while Microsoft technologies treats urls as case-insensitive. (More on my blog post)
NuGet Package: https://www.nuget.org/packages/LowercaseDashedRoute/
To install it, simply open the NuGet window in the Visual Studio by right clicking the Project and selecting NuGet Package Manager, and on the "Online" tab type "Lowercase Dashed Route", and it should pop up.
Alternatively, you can run this code in the Package Manager Console:
Install-Package LowercaseDashedRoute
After that you should open App_Start/RouteConfig.cs and comment out existing route.MapRoute(...) call and add this instead:
routes.Add(new LowercaseDashedRoute("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home", action = "Index", id = UrlParameter.Optional }),
new DashedRouteHandler()
)
);
That's it. All the urls are lowercase, dashed, and converted implicitly without you doing anything more.
Open Source Project Url: https://github.com/AtaS/lowercase-dashed-route
Here's what I did using areas in ASP.NET MVC 5 and it worked liked a charm. I didn't have to rename my views, either.
In RouteConfig.cs, do this:
public static void RegisterRoutes(RouteCollection routes)
{
// add these to enable attribute routing and lowercase urls, if desired
routes.MapMvcAttributeRoutes();
routes.LowercaseUrls = true;
// routes.MapRoute...
}
In your controller, add this before your class definition:
[RouteArea("SampleArea", AreaPrefix = "sample-area")]
[Route("{action}")]
public class SampleAreaController: Controller
{
// ...
[Route("my-action")]
public ViewResult MyAction()
{
// do something useful
}
}
The URL that shows up in the browser if testing on local machine is: localhost/sample-area/my-action. You don't need to rename your view files or anything. I was quite happy with the end result.
After routing attributes are enabled you can delete any area registration files you have such as SampleAreaRegistration.cs.
This article helped me come to this conclusion. I hope it is useful to you.
Asp.Net MVC 5 will support attribute routing, allowing more explicit control over route names. Sample usage will look like:
[RoutePrefix("dogs-and-cats")]
public class DogsAndCatsController : Controller
{
[HttpGet("living-together")]
public ViewResult LivingTogether() { ... }
[HttpPost("mass-hysteria")]
public ViewResult MassHysteria() { }
}
To get this behavior for projects using Asp.Net MVC prior to v5, similar functionality can be found with the AttributeRouting project (also available as a nuget). In fact, Microsoft reached out to the author of AttributeRouting to help them with their implementation for MVC 5.
You could write a custom route that derives from the Route class GetRouteData to strip dashes, but when you call the APIs to generate a URL, you'll have to remember to include the dashes for action name and controller name.
That shouldn't be too hard.
You can define a specific route such as:
routes.MapRoute(
"TandC", // Route controllerName
"CommonPath/{controller}/Terms-and-Conditions", // URL with parameters
new {
controller = "Home",
action = "Terms_and_Conditions"
} // Parameter defaults
);
But this route has to be registered BEFORE your default route.
If you have access to the IIS URL Rewrite module ( http://blogs.iis.net/ruslany/archive/2009/04/08/10-url-rewriting-tips-and-tricks.aspx ), you can simply rewrite the URLs.
Requests to /my-controller/my-action can be rewritten to /mycontroller/myaction and then there is no need to write custom handlers or anything else. Visitors get pretty urls and you get ones MVC can understand.
Here's an example for one controller and action, but you could modify this to be a more generic solution:
<rewrite>
<rules>
<rule name="Dashes, damnit">
<match url="^my-controller(.*)" />
<action type="Rewrite" url="MyController/Index{R:1}" />
</rule>
</rules>
</rewrite>
The possible downside to this is you'll have to switch your project to use IIS Express or IIS for rewrites to work during development.
I'm still pretty new to MVC, so take it with a grain of salt. It's not an elegant, catch-all solution but did the trick for me in MVC4:
routes.MapRoute(
name: "ControllerName",
url: "Controller-Name/{action}/{id}",
defaults: new { controller = "ControllerName", action = "Index", id = UrlParameter.Optional }
);

Resources