Viewengine using a static class on controller view method - c#

I am working on themes at present and the majority of work is going well am just at the phase where I am figuring out what view to present to the user.
This would be on a controller method so far I have.
public static class ThemeViewExtensions
{
public static string GetViewPath(RazorView view , string viewName = "Default")
{
var theme = "Default";
var themeViewPath = $"/Themes/{theme}{view.ViewPath}/{viewName}.cshtml";
return viewPath;
}
}
But for some reason I cannot access it here yes I have my using statement in
public IActionResult Index()
{
return View(this.GetViewPath());
}
What I want to happen is to be able to use it as above and be able to get the ViewPath and name that is being called for example /Controller/Action/ViewName whatever cshtml is that possible on a view?

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);

ASP.NET Core - acessing object from razor in code behind

I'm new to ASP.NET Core, so I'm still trying to understand it.
I was trying to access an object from code behind. Is it possible? I have been trying and searching for hours and I have yet to understand it. I'll leave some of what I've tried bellow, which apparently isn't working. If I could get some insight on this I'd very much appreciate it.
In classic ASP.NET I always to the id approach from code behind to the client side. But now I've been asked to try a different approach. In this case, a loop. How can I do this? I've also been reading the microsoft documentation but I still can't understand this. I'd appreciate some help.
Here's a spinnet of what I tried:
// The controller
public class HomeController : Controller
{
public GetInfo GetInfo { get; set; }
public IActionResult Offers()
{
GetInfo = new GetInfo();
GetInfo.GetOffers();
return View();
}
}
// The GetInfo class which gets data from a JSON file
public class GetInfo
{
public Offer.RootObject Offers { get; set; }
public void GetOffers()
{
var client = new RestClient("whatever.com");
// client.Authenticator = new HttpBasicAuthenticator(username, password);
var request = new RestRequest("Home/GetOffersJson", Method.GET);
IRestResponse response = client.Execute(request);
var content = response.Content;
var obj = JsonConvert.DeserializeObject<Offer.RootObject>(content);
Offers = new Offer.RootObject
{
total = obj.total,
data = obj.data
};
}
}
// The View file, where I'm trying to access the object from c#, which supposedly is 'loaded'
#model HostBookingEngine_HHS.Models.GetInfo;
#{
ViewData["Title"] = "Offers";
}
#foreach (var item in Model.Offers.data)
{
<span asp-validation-for="#item.TextTitle"></span>
}
Thanks, in advance.
asp.net core view has four over loading these are
public virtual ViewResult View();
public virtual ViewResult View(string viewName, object model);
public virtual ViewResult View(object model);
public virtual ViewResult View(string viewName);
ASP.NET Core can use all of these with exact parameter
You can pass your model object using Public Virtual ViewResult View(object model);
You need to pass your model object like this
return View(GetInfo);
which actually access your parameter object
#model HostBookingEngine_HHS.Models.GetInfo;
Also there are several method of passing data to view
Like ViewBag,ViewData which also grab the value in each request.
To have Offers available on view, you have to pass model to a view as an argument to View() method. So in your case you have to make your action look like:
public IActionResult Offers()
{
GetInfo = new GetInfo();
GetInfo.GetOffers();
return View(GetInfo);
}

Is it possible to assign multiple actions to the same controller method?

I have a simple MVC Controller that returns a list of files from folders based on View action. Index() action contains a list of Collections so then user clicks on CollectionOne, the corresponding view is populated. The same behavior is applied to other collections.
The problem is, I have a lot redundant code that I have been able to manage at certain degree by using a private ActionContent() method invoked by all actions. So whenever I have a new collection to add to website, I just add a ActionResult for this Collection and invoke ActionContent() method.
Is there any way to optimize this code to make it more dynamic, without adding a new ActionResult every time a need to post a new collection?
My controller looks like this:
public class PortfolioController : Controller
{
public ActionResult CollectionOne()
{
return View(ActionContent());
}
public ActionResult CollectionTwo()
{
return View(ActionContent());
}
private IEnumerable<string> ActionContent()
{
const string folder = #"~/Content/images/portfolio/";
var path = folder + ControllerContext.RouteData.Values["action"];
var files = Directory
.EnumerateFiles(Server.MapPath(path))
.Select(Path.GetFileName);
return files;
}
}
I want to turn it into something like this (to avoid redundancy) using ActionNames or maybe proper route mapping:
public class PortfolioController : Controller
{
[ActionName("CollectionOne")]
[ActionName("CollectionTwo")]
[ActionName("CollectionThree")]
public ActionResult PortfolioCollection()
{
const string folder = #"~/Content/images/portfolio/";
var path = folder + ControllerContext.RouteData.Values["action"];
var files = Directory
.EnumerateFiles(Server.MapPath(path))
.Select(Path.GetFileName);
return View(files);
}
}
That's what parameters are for:
public ActionResult PortfolioCollection(string id)
{
const string folder = #"~/Content/images/portfolio/";
var files = Directory
.EnumerateFiles(Server.MapPath(folder + id))
.Select(Path.GetFileName);
return View(files);
}
You can make a custom route to assign any URL pattern you want.

View to String from another controller

i have done as Vdex suggested here:
https://stackoverflow.com/a/5801502/973485
And used the RenderPartialToString method he found. And it works perfectly like this:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Test()
{
string t = ViewToString.RenderPartialToString("Index", null, ControllerContext);
return Content(t);
}
}
But if i want to render the Home > Index from another Controller, i get:
Value cannot be null.
Parameter name: controllerContext
Like this:
public class FooController : Controller
{
public ActionResult Index()
{
string t = ViewToString.RenderPartialToString("Index", null, new HomeController().ControllerContext);
return Content(t);
}
}
Is there any way to pass a View from another Controller to a string? I have tried many different methods, and it all of them fails at the ControllerContext. Many thanks!
Update: Why i need to do this:
Imagine i have a website full of widgets, the amount of widgets on each page is dynamic, so i cannot hardcode them in my cshtml file. But in that file there are different areas defined where the widgets gets printet out. To print out these widget i have a list of IWidgetController wich contains alle the different Widgets available, and the interface sais that they need to containe a ActionResult for edit, new and view. example of widgets: CalenderController, NewsController, GalleryController and so on... So in those areas i need to print out the content of each of those Controllers. Now i could also load the URLHTML but i figured doing it from the inside would be faster... right?
Try this:
string t = ViewToString.RenderPartialToString("Index", null, this.ControllerContext);
Anyway, why do you need to convert to a string?

How to pass Form Values to the Controller in .NET MVC

In ASP.net MVC:
How should/Can I pass Form data (From the View) to the Controller?
This is the way I am heading :
The Controller Index function is passing a ViewModel object to the View.
The ViewModel object contains a paginated list as well as some SelectLists.
_ The ViewModel object also contains a custom class I named theFilter. This class' purpose is to hold the Filter information Posted from the View via a Form.
I want the Index [AcceptVerbs(HttpVerbs.Post)] function to receive theFilter object populated with the form data, as well as the page number (as is it right now)
Here are snippets of my code:
The Controller/Index postback function:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(int? page, CaseFilter caseFilter)
{
const int pageSize = 10;
var cases = caseRepo.FindAllCases();
var paginatedCases = new PaginatedList<Case>(cases, page ?? 0, pageSize);
return View(new CaseIndexViewModel(paginatedCases, caseFilter));
}
The Filter Class:
public class CaseFilter
{
int iVolume_id = 0,
iSubject_id = 0;
public CaseFilter() {
}
public int Volume_id { get { return iVolume_id; } set { iVolume_id = value; } }
public int Subject_id { get { return iSubject_id; } set { iSubject_id = value; } }
}
And the ViewModel class:
public class CaseIndexViewModel
{
public PaginatedList<Case> PaginatedCases { get; private set; }
public CaseFilter CaseFilter { get; private set; }
public CaseIndexViewModel(PaginatedList<Case> paginatedCases, CaseFilter caseFilter)
{
PaginatedCases = paginatedCases;
CaseFilter = caseFilter;
}
}
Basically I am trying to avoid using Request.Form to populate the Filter class, at least not to use it within the Controller.
Any help, suggestions or disses are welcome!
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(FormCollection collection)
{
string valueFromNameTextBox = collection["name"];
}
You can index into this collection with the names of all the inputs on the form.
Rather than complicate my method signatures, I've taken to using the ValueProvider property and Try/UpdateModel in the Controller to retrieve form/route values unless the values are simple properties. On the other hand, I would probably also not make the filter part of the model for the View -- I tend to have a narrower conception of the model for the page, wanting it rather to be the business model rather that a model of all the data on the page -- and would simply pass the filter values via ViewData.
To expand BFree's answer, you can go through all the elements in the form by doing something like this:
foreach (string key in collection.keys) {
if (key.contains("blah"))
text1 = collection[key];
}
If it has too many elements for the key.contains if, it can get a bit ugly though, so beware ;).
Finally, I do not need to even use the Request Collection. The CaseFilter object is filled automatically as I set it as a parameter in
public ActionResult Index(int? page, CaseFilter caseFilter)
The code above works as it is.

Categories