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.
Related
I am attempting to return a CreatedAtAction() result from within an ApiController "NotUnitsController" which indicates a route on a separate ApiController "UnitsController".
Everything works as expected until generating the CreatedAtAction() response. I am receiving the error:
System.InvalidOperationException: No route matches the supplied values.
I am not sure what I am missing. I have tried to remedy the issue with the following:
Verified the spelling of the route parameters on both controllers
Attempted to provide a route name to the [HttpGet] in the UnitsController & reference that name from the NotUnitsController.
Added a / to the [HttpGet("/{unitKey}")] route as suggested in this answer.
The v1 portion of the routes are hard-coded. I found that it could be an issue with dynamic route versioning on this GitHub issue.
I am also able to perform GET requests against the UnitsController endpoint. Only the CreatedAtAction() response is failing.
Here are snippets of the two controllers in question.
NotUnitsController:
[ApiController]
[Route("v1/not-units/{notUnitsKey:guid}/units")]
public class NotUnitsController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Post(Guid notUnitsKey, Input unitInput)
{
// TODO: Create Unit
var unitKey = Guid.NewGuid();
return CreatedAtAction(actionName: nameof(UnitsController.Get),
controllerName: nameof(UnitsController),
routeValues: new { unitKey },
value: new { unitKey });
}
}
UnitsController:
[ApiController]
[Route("v1/units")]
public class UnitsController : ControllerBase
{
[HttpGet("{unitKey:guid}")]
public async Task<IActionResult> Post(Guid unitKey)
{
// TODO: Get Unit by key
var unit = $"My Unit with Id: {unitKey}";
return Ok(unit);
}
}
Any help would be greatly appreciated.
Probably you should use nameof(UnitsController.Post) as the actionName value, and new { unitKey = unitKey } as the routeValues value on your CreatedAtAction response.
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.
What is the correct way to redirect request from www.mysite.com to www.mysite.com/swagger?
I setup an index controller Decorated with Route("") and it works but seems like a Kludge. I assume there should be a way to specify it in the MVC routing in Startup.cs, I just can't figure it out.
// kludge code, how do I do this in app.UseMvc(route => route.MapRoute...
[ApiExplorerSettings(IgnoreApi = true)]
[Route("")]
public class IndexController : Controller
{
public ActionResult Index()
{
return Redirect("/swagger");
}
}
Try the following redirect rule:
var option = new RewriteOptions();
option.AddRedirect("^$", "swagger");
app.UseRewriter(option);
I have scenario to return Images using action filter before hit the Specific action. Can we achieve this using action filters? Here we return image as content, if we encounter any image files in url, otherwise pass to respective action.
Yes you can. Consider this as an example:
public class MyFileFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// imagine in your route data you have a key named file
// which contain file name
// if you want access to request url to check if it is a file use
// filterContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath
// property instead
if (filterContext.RouteData.Values.ContainsKey("file"))
{
// very simple example how to find file on the server
string physicalFilePath=HttpContext.Current.Server.MapPath(
filterContext.RouteData.Values["file"].ToString());
// if your url matches and you found a file set
// filterContext.Result value otherwise nothing.
if(File.Exists(physicalFilePath))
{
// also you must send a proper content type for each file.
filterContext.Result = new FilePathResult(physicalFilePath, "YourFileContentType");
}
}
}
}
At the end simply use your Attribute:
[MyFileFilter]
public ActionResult MyAction()
{
// your code
}
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