I am searching for a way to make an application helper that allows you to define methods that you can call in all the controllers views. In Rails you get that for free but how can I accomplish this in ASP.NET MVC with c#?
The usual way is by writing extension methods to HtmlHelper - for example:
public static string Script(this HtmlHelper html, string path)
{
var filePath = VirtualPathUtility.ToAbsolute(path);
return "<script type=\"text/javascript\" src=\"" + filePath
+ "\"></script>";
}
Now in the view you can use Html.Script("foo"); etc (since the standard view has an HtmlHelper member called Html). You can also write methods in a base-view, but the extension method approach appears to be the most common.
I would suggest adding an extension method to the base controller class.
public static class ControllerExtensions
{
public static string Summin(this Controller c)
{
return string.Empty;
}
}
You can access the helper function in your controller:
public class MyController : Controller
{
public ActionResult Index()
{
this.Summin();
return View();
}
}
Related
I have a series of dynamic links on my view that render like this:
Course Image <strong>(600x384)</strong>
I have a method that looks like this:
public class CantoDownloadPreset
{
public static FileResult DownloadPreset(string preset)
{
...
}
}
how can I call the helper DownloadPreset method and send data-itemid parameter.
CantoDownloadPreset should be a controller in a named file CantoDownloadPresetController.cs inside Controllers Folder.
Then if you need to do a GET, inherit Controller and remove static:
public class CantoDownloadPresetController : Controller
public FileResult DownloadPreset(string preset)
{
...
}
But take consideration if you want to return a FileResult and where. This question implies a lot of research about how MVC Web Controller works and you should be more in focus. But anyway, the link should be then (only for GET of course):
/method/action/value
then:
/CantoDownloadPreset/DownloadPreset/?preset=value
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);
I've created extension method like below
public static class RBACExtension
{
public static bool HasPermission(this ControllerBase controller, string permission)
{
// some implementation
}
}
It works fine inside a controller
public class HomeController : Controller
{
public ActionResult Index()
{
this.HasPermission("somePermission");
return View();
}
}
But it doesn't work inside Razor. When I want to use the method it doesn't show up on autocomplete.
ViewContext.Controller.HasPermission("somePermission")
How to make it available inside Razor view?
put extension method in a namespace like
namespace mynamespace.extensions{
public static class RBACExtension
{
public static bool HasPermission(this ControllerBase controller, string permission)
{
// some implementation
}
}
}
and put a using statement at top of your view like
#using mynamespace.extensions
Then this method will be available in your view
ViewContext.Controller is the Controller class, not your base controller class. This is a bit of an awkward way to do this though as it couples your controllers to your views. Instead make an extension method (as a custom HtmlHelper is a great way), for exmaple:
public static class MyExtensions
{
public static bool HasPermission(this HtmlHelper helper, string permission)
{
// blah
}
}
And use in Razor like this:
#Html.HasPermission("admin")
Alternatively put the method in a class inherited from WebViewPage for use in your Razor view. For example:
public abstract class MyBaseViewPage : WebViewPage
{
public bool HasPermission(string permission)
{
// blah
}
}
Then make your Razor pages use it by editing your Views/web.config
<pages pageBaseType="YourProject.MyBaseViewPage">
And now in Razor you can simply do this:
#HasPermission("admin")
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);
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/