How to use Url.Action() in a class file? - c#

How can I use Url.Action() in a class file of MVC project?
Like:
namespace _3harf
{
public class myFunction
{
public static void CheckUserAdminPanelPermissionToAccess()
{
if (ReferenceEquals(HttpContext.Current.Session["Loged"], "true") &&
myFunction.GetPermission.AdminPermissionToLoginAdminPanel(
Convert.ToInt32(HttpContext.Current.Session["UID"])))
{
HttpContext.Current.Response.Redirect(Url.Action("MainPage", "Index"));
}
}
}
}

You will need to manually create the UrlHelper class and pass the appropriate RequestContext. It could be done with something like:
var requestContext = HttpContext.Current.Request.RequestContext;
new UrlHelper(requestContext).Action("Index", "MainPage");
However, you are trying to achieve redirection based on authentication. I suggest you look at implementing a custom AuthorizeAttribute filter to achieve this kind of behavior to be more in line with the framework

Pass the RequestContext to your custom class from the controller. I would add a Constructor to your custom class to handle this.
using System.Web.Mvc;
public class MyCustomClass
{
private UrlHelper _urlHelper;
public MyCustomClass(UrlHelper urlHelper)
{
_urlHelper = urlHelper;
}
public string GetThatURL()
{
string url=_urlHelper.Action("Index", "Invoices");
//do something with url or return it
return url;
}
}
You need to import System.Web.Mvc namespace to this class to use the UrlHelper class.
Now in your controller, create an object of MyCustomClass and pass the controller context in the constructor,
UrlHelper uHelp = new UrlHelper(this.ControllerContext.RequestContext);
var myCustom= new MyCustomClass(uHelp );
//Now call the method to get the Paging markup.
string thatUrl= myCustom.GetThatURL();

#Simon Belanger's answer is perfectly working, but UrlHelper.Action() generates relative URLs and in my case i need the fully qualified absolute URL. So what i need to do is - i have to use one of the overload provided by UrlHelper.Action() method.
var requestContext = HttpContext.Current.Request.RequestContext;
string link = new UrlHelper(requestContext).Action("Index", "Home", null, HttpContext.Current.Request.Url.Scheme);
So let say if your application hosted on "https://myexamplesite.com" then above code will give you full url like this - "https://myexamplesite.com/Home/Index". Hope this answer will help those readers who will come across this link.

For those arriving late to this post, using .Net Core and .Net 5.0, You should try this;
private readonly IUrlHelperFactory _urlHelperFactory;
private readonly IActionContextAccessor _actionContextAccessor;
public EmailSenderService(IUrlHelperFactory urlHelperFactory,
IActionContextAccessor actionContextAccessor)
{
_urlHelperFactory = urlHelperFactory;
_actionContextAccessor = actionContextAccessor;
}
private string GenerateUrl(string action, string controller, object routeValues = null)
{
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
return urlHelper.Action(action, controller, routeValues, _actionContextAccessor.ActionContext.HttpContext.Request.Scheme);
}

I tried to use #simion's answer and I was getting an invalid type in the constructor for UrlHelper. "cannot convert from System.Web.Routing.RequestContext to System.Net.Http.HttpRequestMessage"
So I used this
var urlHelper = new System.Web.Mvc.UrlHelper(HttpContext.Current.Request.RequestContext);
string url = urlHelper.Action("MainPage", "Index");
worked out for me.

You can also use this
return RedirectToAction("Index", "MainPage");

You simply need to pass Url property from your controller to your class file,
string CreateRoutingUrl(IUrlHelper url)
{
return url.Action("Action", "Controller");
}
and on your controller :
MyClass.CreateRoutingUrl(Url);

Related

Extension method to get a controller name without suffix

I don't want to repeat the following
nameof(HomeController).Replace(nameof(Controller), string.Empty)
everytime I need to supply controller name without suffix "Controller".
Is there any elegant way to simplify? I attempted to create an extension method as follows but I don't know how to get the instance of the controller in question from a cshtml view.
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1
{
public static class Utilities
{
public static string BareName(this Controller controller)
{
return nameof(controller.GetType).Replace(nameof(Controller), string.Empty);
}
}
}
I want to invoke the following, for example, in view page.
#Html.ActionLink("something", nameof(HomeController.someaction), nameof(HomeController).Replace(nameof(Controller), string.Empty))
You could create a separate extension method on ActionContext which resolves the controller name from the ActionDescriptor.
public static string GetControllerName(this ActionContext actionContext)
{
return (actionContext.ActionDescriptor as ControllerActionDescriptor)?.ControllerName;
}
You don't need to trim the "Controller" part from the string in this case. Call it in your view like this:
var controllerName = ViewContext.GetControllerName();
Assuming that you want the name of the controller which is not currently handling the request, you want something like this:
public static string BareName<T>() where T: Controller
{
return typeof(T).Name.Replace(nameof(Controller), string.Empty);
}
You can then use it like:
#Html.ActionLink("something", nameof(HomeController.someaction), Utilities.BareName<HomeController>())
I'm still not sure why Microsoft hasn't put this as part of the ASP.NET Core core package...
If you're in a view, just use ViewContext.RouteData.Values["controller"].
UPDATE (based on edited question)
So, yeah, it wasn't clear before what you were actually trying to achieve. Basically, you want to be able to specify the controller/action params of methods like Html.Action without using hard-coded strings. As far as extension methods go, your best bet is an IHtmlHelper extension like:
public static class IHtmlHelperExtensions
{
public static string GetControllerName<TController>(this IHtmlHelper helper)
where TController : Controller
{
return typeof(TController).GetName().Replace("Controller", "");
}
}
Which you can then use like:
Html.GetControllerName<HomeController>()
However, this still feels clunky to me. Honestly, if you're looking to avoid hard-coding things, I'd recommend naming your routes and using a static class to house the names:
public static class RouteNames
{
public const string Home = "Home";
}
Then:
public class HomeController : Controller
{
[HttpGet("", Name = RouteNames.Home)]
public IActionResult Index() => View();
}
And then finally:
#Html.RouteLink(RouteNames.Home, RouteNames.Home)
Or via tag helper:
<a asp-route="#RouteNames.Home">#RouteNames.Home</a>
If you want to get controller, action or area name inside a view or controller, here are some options:
you just need a reference to an ViewContext or HttpContext or IHtmlHelper object:
////////////////////////////////
// with an IHtmlHelper object //
////////////////////////////////
var request = htmlHelper.ViewContext.HttpContext.Request;
var controllerName = request.RouteValues["controller"].ToString();
var actionName = request.RouteValues["action"].ToString();
// if area exists
var areaName = request.RouteValues["area"].ToString();
//////////////////////////////////
// with an `ViewContext` object //
//////////////////////////////////
var controllerName = ViewContext.RouteData.Values["controller"].ToString();
var actionName = ViewContext.RouteData.Values["action"].ToString();
// if area exists
var areaName = ViewContext.RouteData.Values["area"].ToString();
So an extension method for IHtmlHelper could be as follows:
public static class IHtmlHelperExtensions
{
public static string GetControllerName(this IHtmlHelper htmlHelper) =>
htmlHelper.ViewContext.RouteData.Values["controller"].ToString();
}
[HttpGet]
public IActionResult Get([FromRoute]string controller)
{
//controller will be Controller name without suffix "Controller".
}
var controllerName = ControllerContext.ActionDescriptor.ControllerName.Replace(nameof(Controller), string.Empty);

Build a route pattern based on Controller type and Acton name

Disclaimer: First of all, I want to mention that I looked all over the internet for an answer, read all the documentation, read all the question I could possibly find here, but no luck so far.
So, here's my case. I am building an API using ASP.NET Core 2.2 and I'm using HATEOAS (HAL specification and Halcyon library). I should provide links along with the resource itself. This what drove me towards HATEOAS in the first place. Some of the links are templated, since it might be a PUT method and id is to be specified by frontend.
The issue is, that my controllers can have very different routes (using attribute-based routing) and hardcoding links is a bad thing,cause if the route changes I need to remember to change the link where it's used as well. For this reason I decided to generate link based on Controller type and Action name. LinkGenerator is what I found, but it seems it returns null, if I don't specify all the parameters for the route. Here's a code example:
[Route("api/metadata")]
[ApiController]
public class MetadataController : ControllerBase
{
private readonly IMetadataProvider _metadataProvider;
private readonly LinkGenerator _linkGenerator;
public MetadataController(
IMetadataProvider metadataProvider,
LinkGenerator linkProvider)
{
_metadataProvider = metadataProvider;
_linkGenerator = linkProvider;
}
[HttpGet]
public IActionResult GetMetadata()
{
var metadata = _metadataProvider.GetMetadata();
// here url will be 'null', because last parameter is null
// and route requires parameter 'name' to be specified instead of 'null'
// EXPECTED: "api/metadata/{name}"
// ACTUAL: null
string url = _linkGenerator.GetPathByAction(
nameof(MetadataController.GetByName),
nameof(MetadataController).Replace(nameof(Controller), string.Empty),
null);
var response = new HALResponse(metadata)
.AddSelfLink(HttpContext.Request)
.AddLinks(new Link(name, url));
return Ok(response);
}
[HttpGet("{name}")]
public IActionResult GetByName(string name)
{
var metadata = _metadataProvider.GetMetadataForEntity(name);
return Ok(metadata);
}
}
How can I generate a link, so that it's not hardcoded and it is templated?
After a couple of hours of debugging the ASP.NET source code, I think I found a way to do this.
It seems, that LinkGenerator is intended to build a complete and valid url, so all the parameters are required. What I was looking for was actually a route pattern.
While debugging, I found a IEndpointAddressScheme<RouteValuesAddress> service injected into LinkGnerator. It is used to actually find the route patern. After that, LinkGenerator tries to fill all the parameters.
Here's the above code fixed and working:
[ApiController]
public class MetadataController : ControllerBase
{
private readonly IMetadataProvider _metadataProvider;
private readonly IEndpointAddressScheme<RouteValuesAddress> _endpointAddress;
public MetadataController(
IMetadataProvider metadataProvider,
IEndpointAddressScheme<RouteValuesAddress> endpointAddress)
{
_metadataProvider = metadataProvider;
_endpointAddress = endpointAddress;
}
[HttpGet]
public IActionResult GetMetadata()
{
var metadata = _metadataProvider.GetMetadata();
// EXPECTED: "api/metadata/{name}"
// ACTUAL: "api/metadata/{name}"
string actionName = nameof(MetadataController.GetById);
string controllerName = nameof(MetadataController).Replace(nameof(Controller), string.Empty);
var url = _endpointAddress.FindEndpoints(CreateAddress(actionName, controllerName))
.OfType<RouteEndpoint>()
.Select(x => x.RoutePattern)
.FirstOrDefault();;
var response = new HALResponse(metadata)
.AddSelfLink(HttpContext.Request)
.AddLinks(new Link(name, url));
return Ok(response);
}
[HttpGet("{name}")]
public IActionResult GetByName(string name)
{
var metadata = _metadataProvider.GetMetadataForEntity(name);
return Ok(metadata);
}
private static RouteValuesAddress CreateAddress(string action, string controller)
{
var explicitValues = new RouteValueDictionary(null);
var ambientValues = GetAmbientValues(httpContext);
explicitValues ["action"] = action;
explicitValues ["controller"] = controller;
return new RouteValuesAddress()
{
AmbientValues = ambientValues,
ExplicitValues = explicitValues
};
}
}

Turning URL into controller / action pair

In .Net MVC, you define routes into a RouteCollection. The URL helper methods make it easy to turn a controller + action + optional params into a URL.
When .Net MVC processes a request from a client browser, it clearly maps this URL to the right controller + action, to execute the appropriate command.
However, I can't see a way to programatically access this routing on the fly, such that I can turn a fully qualified URL (or a list of 10k+ URLs) into it's route components.
Does anyone know how you'd turn, for example, the following string input:
"http://stackoverflow.com/questions/2342325/c-sharp-net-mvc-turning-url-into-controller-action-pair"
into the following output:
{
controller: "questions",
action: "view",
id: 2342325,
seoText: "c-sharp-net-mvc-turning-url-into-controller-action-pair"
}
Given this mapping is clearly being done by .Net, is it exposed anywhere?
Why would anyone want to do this?
Imagine you have a list of URLs you know have been accessed, mostly dynamic in nature, for example stackoverflow.com/questions/2342325/c-sharp-net-mvc-turning-url-into-controller-action-pair, and you want to work out which actual endpoints / actions / controllers are being hit programatically (without much care about the actual data being passed).
You could hand code mappings, such that you know /questions/{id}/{text} -> controller: questions, action: question, but that's not future-proof, nor is it fun, and relies on text manipulation / processing.
Given a Route Dictionary and a list of URLs, with a function as described above, you could look at which controllers are most hit, or which actions, etc.
You should take a look at creating your own MvcRouteHandler. This is the point in the MVC stack where the Route Engine has already parsed the URL to find which Controller and Action to call, and then it goes through this method to get the actual C# class and method to invoke. No authorization or even HTTP Verb has been applied yet, so you will see every call that is made to your application.
public class CustomRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext context)
{
var controller = context.RouteData.Values["controller"];
var action = context.RouteData.Values["action"];
// Do whatever logging you want with this data, maybe grab the other params too.
return base.GetHttpHandler(context);
}
}
This can easily be registered where you set up your Routing.
routes.MapRoute("Home", "{controller}/{action}", new
{
controller = "Home",
action = "Index"
})
.RouteHandler = new CustomRouteHandler();
Looks like the only way to do this is by creating a dummy HTTP Context, similar to how you would unit test routes. It's a shame MVC doesn't provide better access to this, given it's being run on every request, rather than wrapping it up inside the context object.
Anyway, here is a working solution which can be modified to suit your needs:
public class UrlToRouteMapper
{
public static RouteValueDictionary GetRouteDataFromURL(string absoluteURL)
{
var testUrl = "~" + new Uri(absoluteURL).AbsolutePath;
var context = new StubHttpContextForRouting(requestUrl: testUrl);
     var routes = new System.Web.Routing.RouteCollection();
     MvcApplication.RegisterRoutes(routes);
System.Web.Routing.RouteData routeData = routes.GetRouteData(context);
return routeData.Values;
}
public static string GetEndpointStringFromURL(string absoluteURL)
{
var routeData = GetRouteDataFromURL(absoluteURL);
return routeData["controller"] + "/" + routeData["action"];
}
}
public class StubHttpContextForRouting : HttpContextBase {
StubHttpRequestForRouting _request;
StubHttpResponseForRouting _response;
public StubHttpContextForRouting(string appPath = "/", string requestUrl = "~/") {
_request = new StubHttpRequestForRouting(appPath, requestUrl);
_response = new StubHttpResponseForRouting();
}
public override HttpRequestBase Request {
get { return _request; }
}
public override HttpResponseBase Response {
get { return _response; }
}
}
public class StubHttpRequestForRouting : HttpRequestBase {
string _appPath;
string _requestUrl;
public StubHttpRequestForRouting(string appPath, string requestUrl) {
_appPath = appPath;
_requestUrl = requestUrl;
}
public override string ApplicationPath {
get { return _appPath; }
}
public override string AppRelativeCurrentExecutionFilePath {
get { return _requestUrl; }
}
public override string PathInfo {
get { return ""; }
}
}
public class StubHttpResponseForRouting : HttpResponseBase {
public override string ApplyAppPathModifier(string virtualPath) {
return virtualPath;
}
}

Get Name of Controller from ActionFilter

I'm writing an ActionFilter and would like to have a more type-safe way to use RedirectToRouteResult. While investigating this, I wondered if there was a way to get the name (as a string) of any of my controllers. So for example, I would like to get "Home" from my HomeController, or "Admin" from my Admin controller. Is this at all possible?
From a filter context you can get the controller name by using:
public class MyFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
//That will give you "HomeController"
var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
//You can remove the "Controller" part, by replacing it with an empty string, like:
var justTheController = controllerName.Replace("Controller", string.Empty);
}
}
Have you tried this. You know you can substitute typeof by GetType on instance variable. if you have HomeController instance homeCtrl....you can do homeCtrl.GetType
var fullName= typeof(HomeController).Name;
var partialName = fullName.Remove(fullName.IndexOf("Controller"));
Actually there are ways to get the controller name :
filterContext.Controller will give you an object from where you
can deduce the controller name as
filterContext.Controller.GetType().Name
You always have the controller in the route values and can deduce as Request.RequestContext.RouteData.Values("controller").ToString()
Write extension method
public static class ControllerStringExtension
{
private const string ControllerString = "Controller";
public static string Short(this string value)
{
if (value.EndsWith(ControllerString))
{
//Remove 'Controller' from end of value name.
return value.Remove(value.Length - ControllerString.Length);
}
throw new ApplicationException("Should be used only for Controller names.");
}
}
than use: Redirect to 'home' controler and 'index' action.
public RedirectToRouteResult Something()
{
return RedirectToAction(nameof(HomeController.Index), nameof(HomeController).Short());
}

Render Controller Action To String (Using Virtual Path, No action/controller name) ASP.NET MVC 3

Ok, im developing a MVC CMS like website and when declaring routes i used following pattern. I encapsulate action name and controller name into a class like so
public class UrlUtilsUnhandledErrorsExtensions
{
private readonly UrlHelper _urlHelper;
public UrlUtilsUnhandledErrorsExtensions(UrlHelper urlHelper)
{
_urlHelper = urlHelper;
}
public String GetLatestErrors()
{
return _urlHelper.Action("GetLatestErrors", "UnhandledErrors");
}
}
Then instead of writing
#Url.Action("GetLatestErrors", "UnhandledErrors")
I write
#Url.Action(Url.Utils().UnhandledErrors().GetLatestErrors())
I find this approach much more easier to maintain, because if controller name changes i only have to change one class.
This works fine with any links, controller redirects (return Redirect(...)) and just anything that accept virtual path which is returned by
public String GetLatestErrors()
{
return _urlHelper.Action("GetLatestErrors", "UnhandledErrors");
}
But here comes the problem: i cant use Html.Action() with this approach. It requires controller name and action name, but instead i want it to use virtual path.
After digging around and studying MVC source code i realized that i will need to write my own Html.Action extension method that will just accept virtual path.
So here is my solution
public void ActionFromUrl(this HtmlHelper htmlHelper, String url)
{
RouteValueDictionary rvd = null;
rvd = new RouteValueDictionary();
String action = String.Empty;
String controller = String.Empty;
foreach (Route route in htmlHelper.RouteCollection)
{
if (route.Url == url.Substring(1)) // url starts with / for some reason
{
action = route.Defaults["action"] as String;
controller = route.Defaults["controller"] as String;
break;
}
}
RequestContext rc = ((MvcHandler)HttpContext.Current.CurrentHandler).RequestContext;
rc.RouteData.Values["action"] = action;
rc.RouteData.Values["controller"] = controller;
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controllerImpl = factory.CreateController(rc, controller);
controllerImpl.Execute(rc);
}
It works, but since its based on Html.RenderAction method it just writes directly to output, so in my view when i write following code
#{ Html.ActionFromUrl(Url.Utils().UnhandledErrors().GetLatestErrors()); }
It renders my partial first, all above everything and then rest of html follows.
This is not the result i want, so i have to find out the way of rendering the result to string as Html.Action do. I already looked into the source code with dotPeek but coudn't figure out how to mix it altogether.
My question is: Am i doing something wrong ? Or how can i write Html.Action overload so it accepts virtual path and returns MvcHtmlString ?
in CMS, you probably do not need the whole convention-based view rendering, you will (sooner or later) want render custom templates "to string" and merge result layout by your (most probably dynamic/configurable) rules. Take a look on RazorEngine project.
http://razorengine.codeplex.com/

Categories