MVC 4 - getting a 404 on a certain area's controllers - asp.net-mvc-4

My problem is regarding setting up my app's access for internal users and external users.
Based on MS TechKB: AREAS in ASP.NET I went with AREAS. I have an Areas/Internal and Areas/External. Areas/External has the actual functionality and controllers for the app. All Areas/Internal does is check server variables and sets your identity as your domain name if it checks out in active directory.
The problem is, I can get to the Areas/External controllers/pages just fine but when I try to browse to the Areas/Internal area controller I get a 404. My controller is named Intranet (excluding the "Controller" in the name) and this is what my InternalAreaRegistration.cs file looks like:
public override void RegisterArea(System.Web.Mvc.AreaRegistrationContext context)
{
context.MapRoute(
"Internal_default",
"Intranet/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional });
}
public override string AreaName
{
get
{
return "Internal";
}
}
Can anyone tell me why I would be getting a 404 on the internal controllers?

It seems you are missing controller segment after Area in the code you wrote above. It should be something like:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Internal_default",
"Intranet/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "Your Controller Namespace" }
);
}
A related question at below link is answered, hope that will help too:
Controller with same name as an area - Asp.Net MVC4

Related

IdentityServer4 client_id specific login pages

I'm trying to have different login pages based on the client_id.
Use case :
My default login page is a classic username/password type login, but for a specific client_id, the login page asks for 3 different infos that are found one a piece of paper that he received in the mail (sent by a third party).
Once i have these 3 infos, i can validate and find the associated user.
Technicals : So far, i have made it so that once IdentityServer4 redirects /connect/authorize to it's default login route (/account/login), i then redirect to my second login based on the client_id. It works but it is not elegant at all (feels hackish).
I'm sure there is a better way to achieve this, probably thru a middleware that would redirect from connect/authorize to my second login page, directly ?
Any ideas / tips ?
On the very initial Login call to IdentityServer, you call:
/// <summary>
/// Show login page
/// </summary>
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// build a model so we know what to show on the login page
var vm = await accountService.BuildLoginViewModelAsync(returnUrl);
// some more code here
return View(vm);
}
In the called accountService.BuildLoginViewModelAsync, you have var context = await interaction.GetAuthorizationContextAsync(returnUrl); and in this context you have the clientId. You can extend the LoginViewModel class to include some custom property (of your own) and based on this property, in the AccountController, to return a different view. Then all you need is in the Views folder to create your specific view.
By this way, you can have as many views as you want.
Instead of creating hard coded separate views I simply use the appsettings.json file and specify different client configurations for each clientId. This way I can easily edit the file in the future any time there is a new client without having to re-deploy.
Then within the AccountController Login method I set the title and image of the current LoginViewModel (you have to add Title and Image to the LoginViewModel class) by matching the current clientid to a matching object within the appsettings.json file. Then set the ViewBag.Title and ViewBag.Image right before returning the view.
For information on how to wire-up the appsettings see my answer on this SO article
in the BuildLoginViewModelAsync(string returnUrl) method within the AccountController I do the following:
if (context?.ClientId != null)
{
try
{
_customClients.Value.ForEach(x => {
if (x.Name == context.ClientId)
{
title = x.Title;
image = x.Image;
}
});
}
catch (Exception){}
...
}
Here is my login method within the AccountController:
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// build a model so we know what to show on the login page
var vm = await BuildLoginViewModelAsync(returnUrl);
if (vm.IsExternalLoginOnly)
{
// we only have one option for logging in and it's an external provider
return RedirectToAction("Challenge", "External", new { provider = vm.ExternalLoginScheme, returnUrl });
}
ViewBag.Title = vm.Title;
ViewBag.Image = vm.Image;
return View(vm);
}
Then in the _Layout.cshtml I use this;
#using IdentityServer4.Extensions
#{
string name = null;
string title = "SomeDefaultTitle";
string image = "~/somedefaulticon.png";
if (!true.Equals(ViewData["signed-out"]))
{
name = Context.User?.GetDisplayName();
}
try
{
title = ViewBag.Title;
image = ViewBag.Image;
}
catch (Exception)
{
}
}
later in the razor I use #title or #image wherever I need either.

Odd routing outcome with ASP.NET MVC 4?

I have the oddest routing problem that I cannot work out how to avoid. We have two areas in our application, and both of them have an Employees controller. So we have these two valid URLs:
blah.com/Employees/Employees
blah.com/Reports/Employees
The routes are registered as follows:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Employees_default",
"Employees/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Reports_default",
"Reports/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
Now everything works as expected, but the problem crops up if someone enters and incorrect URL, like this one:
blah.com/Employees
which then generates the following error:
Multiple types were found that match the controller named 'Employees'. This can happen if the route that services this request ('{controller}/{action}/{id}') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
The request for 'Employees' has found the following matching controllers:
blah.Admin.Areas.Employees.Controllers.EmployeesController
blah.Areas.Reports.Controllers.EmployeesController
It doesn't make sense to me that it would even try to match either of those two routes when the area is missing from the route? The routes clearly include the area name in the route, and it is not optional?
I also noticed that if I have another valid controller for another route, say this one:
blah.com/Tools/ErrorLog
that if I enter blah.com/ErrorLog it actually RUNS that Tools/ErrorLog controller, but it blows up attempting to find the views. Any ideas what is up here?
You need to use NameSpaces in your MapRoute method.
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Employees_default",
"Employees/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new [] { Mysolution.Myproject.Employees }
);
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Reports_default",
"Reports/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new [] { Mysolution.Myproject.Reports }
);
}

Determining the controller from a raw URL in ASP.NET MVC4

Given the following URL: http://www.domain.com/Client
is it possible to access the Route Data in a controller to determine which Controller/Action that is bound to?
It should be pretty simple to determine the controller from the RouteData dictionary, passing the key that you are looking for.
namespace UI.Controllers
{
[Authorize]
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
var controllerName = RouteData.Values["controller"];
//controllerName == "Home" at this point
var actionName = RouteData.Values["action"];
//actionName == "Index" at this point
return View("Index");
}
}
}
EDIT
I have found some information regarding how to do this here: but, you will need to change your absolute URLs back to relative URLs before you can run them through the solution provided.

Routing GET and POST routes in ASP.NET MVC 4

I am trying to setup a Login form in an ASP.NET MVC 4 app. Currently, I have configured my view as shown here:
RouteConfig.cs
routes.MapRoute(
"DesktopLogin",
"{controller}/account/login",
new { controller = "My", action = "Login" }
);
MyController.cs
public ActionResult Login()
{
return View("~/Views/Account/Login.cshtml");
}
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model)
{
return View("~/Views/Account/Login.cshtml");
}
When I attempt to visit /account/login in the browser, I receive an error that says:
The current request for action 'Login' on controller type 'MyController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Login() on type MyApp.Web.Controllers.MyController
System.Web.Mvc.ActionResult Login(MyApp.Web.Models.LoginModel) on type MyApp.Web.Controllers.MyController
How do I setup a basic form in ASP.NET MVC 4? I've looked at the sample Internet App template in ASP.NET MVC 4. However, I can't seem to figure out how the routing is wired up. Thank you so much for your help.
I haven't tried this yet but can you try annotating your Login actions with the appropriate Http Verb - I'm assuming that you're using a GET for viewing the login page and a POST for processing the login.
By adding [HttpGet] for the first action and [HttpPost] for the second action the theory is that ASP.Net's routing will then know which Action method to call based upon which method has been used. Your code should then look something like this:
[HttpGet] // for viewing the login page
[ViewSettings(Minify = true)]
public ActionResult Login()
{
return View("~/Views/Account/Login.cshtml");
}
[HttpPost] // For processing the login
[ViewSettings(Minify = true)]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model)
{
return View("~/Views/Account/Login.cshtml");
}
If this doesn't work, consider having two routes and two differently named actions like below:
routes.MapRoute(
"DesktopLogin",
"{controller}/account/login",
new { controller = "My", action = "Login" }
);
routes.MapRoute(
"DesktopLogin",
"{controller}/account/login/do",
new { controller = "My", action = "ProcessLogin" }
);
There are other similar questions and answers on StackOverflow already, take a look at: How to route GET and DELETE for the same url and there is also the ASP.Net documentation which might also help.

Trouble getting a response from an asp.NET WebAPI in an Area

I'm trying to add a ASP.NET Web API ( from MVC 4 ) to my project..... But I'm having some trouble getting any response from the Area/WebAPI/Controller ( not quite sure where it's going wrong...)
I installed the Route Debugger, and if I go to my main page... I see the routes...
Matches Current Request Url Defaults Constraints DataTokens
False api/{controller}/{action}/{id} action = Index, id = UrlParameter.Optional (empty) Namespaces = OutpostBusinessWeb.Areas.api.*, area = api, UseNamespaceFallback = False
False {resource}.axd/{*pathInfo} (null) (empty) (null)
True {controller}/{action}/{id} controller = Home, action = Index, id = UrlParameter.Optional (empty) (empty)
True {*catchall} (null) (null) (null)
So it seems like the route is setup
Next I have a PlansController in the "api" Area, It's just the default apiController generated by "Add New"...
public class PlansController : ApiController
{
// GET /api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET /api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST /api/<controller>
public void Post(string value)
{
}
// PUT /api/<controller>/5
public void Put(int id, string value)
{
}
// DELETE /api/<controller>/5
public void Delete(int id)
{
}
}
Now When I go to http://localhost:2307/api/Plans/1
I get
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /api/Plans/1
Any ideas why? Is there something I need to configure?
ASP.NET MVC 4 doesn't support WebApi in areas out of the box.
Martin Devillers proposed a solution: ASP.NET MVC 4 RC: Getting WebApi and Areas to play nicely
You can also have more details in my response on a similar question (especially for support with portable areas):
ASP.Net WebAPI area support
Change it to:
// GET /api/<controller>
public IEnumerable<string> GetMultiple(int id)
{
return new string[] { "value1", "value2" };
}
Call it with:
http://localhost:2307/api/Plans/GetMultiple/1
This is my Global.asax:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
My Controller:
public class MyApiController : ApiController
{
public IQueryable<MyEntityDto> Lookup(string id) {
..
}
I called it as follows:
http://localhost/MyWebsite/api/MyApi/Lookup/hello
Works perfectly.