Calling controller action method directly from Razor View - c#

I looked around and couldn't find an easy solution.
I've tried #GetUserName which doesn't work.
I've tried # { GetUserName which doesn't work.
There has to be an easy way to call a method from the razor view engine.
It is within a foreach loop.
I need GetUserName(item.userID)
The below code is in my controller:
[ChildActionOnly]
public string GetUserName(int userID)
{
ProPit_User user = db.ProPit_User.Find(userID);
return user.username;
}

Trying to call a controller action method directly from your view is usually a sign of bad design.
You have a few options, depending on what you are trying to do:
Avoid calling the method, instead put the data in your model, and render the model
Use Html.RenderAction
Put the method in another class and use normal function syntax.
(1) is usually my default approach, often incorporating DisplayTemplates and EditorTemplates
For (3), e.g.
public static class Util
{
public string MyUtilMethod(int blah)
}
And the view:
#Util.MyUtilMethod(1)

Although you can obtain the controller instance from your view, doing so is plain wrong as it violates the whole MVC (and MVVM) paradigm, as the view should not be aware of its controller.
(The only possible reason I can think of where this would be useful would perhaps be for testing the controller from a mocked view, although even here, it should be possible to test the exposed controller functionality directly from unit tests):
#{
var controller = ViewContext.Controller as MyController;
var userName = controller.GetUserName(123);
}
The better way to have arrived at this result is for the controller to pre-populate, and pass all the data needed by the View, such as the userName, to a custom ViewModel (as typed by the #model directive at the top of the Razor page), or to the ViewBag dynamic.

Related

ASP.NET MVC 5 passing data from home controller to layout via ViewBag

In my HomeController.cs I have some data, which I need to pass to my /Shared/_Layout.cshtml via ViewBag. But I have no idea, how can I do it.
This is my /Shared/_Layout.cshtml
#foreach (var item in ViewBag.Order)
{
<li>#item.Name [ #item.Count ]</li>
}
And here is HomeController.cs
public ActionResult Index()
{
ViewBag.Order = SELECT FROM DB -> ADD TO LIST
}
You don't have access to the Viewbag in the Layout View unfortunately.
You could however use:
PartialView call to a method in a base controller
Simply define a base controller that is a parent for all your controllers. This can also be handy for some error handling by the way.
And in your layout use #Html.Partial("ViewName") to call your base controller.
Use Ajax call
Use javascript in your layout view to execute a controller function that returns the data you need.
Use Session variables instead
since session variables are accesable in layout views.
There are probably more answers too, but I believe these will probably be the most common solutions to your problem.
(If you need any help implementing one of these solutions please give me a sign and I'll explain it more deeply how to do it.)
A fairly common method of passing content to the layout, is to let all ViewModel classes implement a base class (or interface), which will then be the #model of your _Layout view.
Assuming you want to pass on some text MyText to all of your pages:
// My base view model class
public class ViewModelBase {
public string MyText { get; set; }
}
// Some other view model
public class MyOtherViewModel : ViewModelBase {
// other properties
}
// In the _Layout view, implement the base class
#model ViewModelBase
...
#Html.DisplayFor(m => m.MyText)
...
#RenderBody()
...
This way, your _Layout view can work with all the properties of the ViewModelbase class, while whatever view is rendered after that still will have the properties of their child view model - here MyOtherViewModel - available.
Hope that helps!
On a side note, I would not recommend an extended use of ViewBags for passing data to your view in MVC, simply because it has a very low maintainability compared to other methods, due to it not being strongly typed. In my opition, ViewBags does not have any real benefits compared to - for instance - using viewmodels.
As an alternative, you can create a base controller inheriting from the controller and assigning the ViewBag variable to your list in the base controller constructor, like so
public class BaseController : Controller {
public BaseController(){
ViewBag.Order = SELECT FROM DB -> ADD TO LIST
}
}
Then in other controllers, inherit from the base controller like so
public class HomeController : BaseController {
}
This then allows you to assign the view bag for each controller request and the view bag variable will be available in the Layout View.
I myself have used this technique to show a list of car makes that is shown throughout the website.
As a suggestion though, I would implement some form of caching, to save constant calls to the back end.
However if you want to keep the functionality in the home controller you will need to restructure it so that it can be used via an Ajax call using jQuery/Javascript as #counterflux has suggested in their second point

In MVC how can I use the controller to render a partial view only for approved users?

In MVC 5 I am attempting to use the controller to render a partial view only if the (Windows Authenticated) user belongs to one or more of a list of Active Directory groups. There are over 30 distinct groups I need to account for, so the "hello world" examples don't fit my needs. After playing scavenger hunt on the web, I managed to collect this much. No compile or runtime errors, but the content is showing for all users rather than the specific users. So the desired outcome is not yet achieved.
While I can achieve the desired outcome using if-then logic in the view, it creates a lot of unnecessary duplication and encourages spaghettification. So I'm trying to do this in the controller.
Summary of Desired Outcome:
When the user loads the viewpage, the partial view should only render if the Windows Authenticated user belongs to one or more of a list of groups defined in the controller action. If the user is not authorized, then the partial view is not included.
Controller Block:
[ChildActionOnly]
[Authorize(Roles="Domain\\GroupA,Domain\\GroupB")]
public ActionResult MonitorCSU()
{
return PartialView("MonitorCSU");
}
View Block:
<div class="rowWithCols3">
#Html.Partial("MonitorCSU")
Unsuccessful Iterations:
In the controller block I tried (unsuccessfully) to use an if-then block, the else case being another partial view with no content.
[ChildActionOnly]
public ActionResult MonitorCSU()
{
if (User.IsInRole("Domain\GroupA")) {
return PartialView("_MonitorCSU");
}
else
{
return PartialView("_Unauthorized");
}
}
In Razor, I tried using HTML.Action but when I tried run the page the browser hung in an infinite loop.
#Html.Partial() returns a partial view without calling a controller method. In order to call your controller method, you need to use
#Html.Action("MonitorCSU")
or
#{ Html.RenderAction("MonitorCSU") }
Note this assumes that the MonitorCSU() method is in the same controller as the method that generates the main view (other wise you also need to include a parameter for the controller name)
Refer documentation
While you've found a solution, you're going to have other problems with it. I would suggest a different approach, which is to use EditorTemplates and create a separate model for the html you want to render. Then, at runtime you would check whether the user is in the groups you specify, and if they are, you create an instance of the model, and if they are not you leave the model null. In this way, when the view is rendered with EditorFor(), it will ignore and not render the template for users who do not have access.
Thanks to #Stephen Muecke and and a commenter whose entry has mysteriously vanished, I have the missing pieces.
I was able to test this code with several real users and verified the desired behavior happens consistently.
Controller Block:
Main difference: take out authorization and use an if-then block send one of two partial views.
[ChildActionOnly]
public ActionResult MonitorCSU()
{
if (User.IsInRole("DOMAIN\\GroupA"))
{
return PartialView("MonitorCSU");
}
else
{
return PartialView("Unauthorized");
// this is an empty page
}
}
View Block:
The key difference is using HTML.Action
<div class="rowWithCols3">
#Html.Action("MonitorCSU")

Using MVC, how do I design the view so that it does not require knowledge of the variables being set by the controller?

Let's say I have a theoretical MVC framework that uses a ViewData object to pass data from the controller to the view. In my controller, let's say I have some code like this (in pseudocode):
function GetClientInfo()
{
// grab a bunch of data from the database
var client = Database.GetClient();
var clientOrders = Database.GetClientOrders();
var clientWishList = Database.GetClientWishList();
// set a bunch of variables in the ViewData object
ViewData.set("client", client);
ViewData.set("clientOrders", clientOrders);
ViewData.set("clientWishList", clientWishList);
showView("ClientHomePage");
}
And then in my ClientHomePage view, I display the data like so:
<p>Welcome back, [ViewData.get("client").FirstName]!</p>
<p>Your order history:</p>
<ul>
[Html.ToList(ViewData.get("clientOrders")]
</ul>
<p>Your wishlist:</p>
<ul>
[Html.ToList(ViewData.get("clientWishList")]
</ul>
This is what I understand MVC to be like (please correct me if I'm wrong). The issue I'm having here is those magic strings in the view. How does the view know what objects it can pull out of the ViewData object unless it has knowledge of what the controller is putting in there in the first place? What if someone does a refactor on one of the magic strings in the controller, but forgets to change it in the view, and gets a runtime bug instead of a compile-time error? This seems like a pretty big violation of separation of concerns to me.
This is where I'm thinking that a ViewModel might come in handy:
class ClientInfo
{
Client client;
List clientOrders;
List clientWishList;
}
Then the controller creates an instance of ClientInfo and passes it to the view. The ViewModel becomes the binding contract between the controller and the view, and the view does not need to know what the controller is doing, as long as it assumes that the controller is populating the ViewModel properly. At first, I thought this was MVVM, but reading more about it, it seems like what I have in mind is more MVC-VM, since in MVVM, the controller does not exist.
My question is, what am I not understanding here about MVC vs. MVVM? Is referring to variables in the ViewData by magic strings really not that bad of an idea? And how does one insure that changes made in the controller won't adversely affect the view?
Your understanding of MVC is wrong, it stands for Model View Controller but you are missing the Model in your example. This is the typed entity that gets passed back to the View to do the rendering. In ASP.Net MVC you would use typed Views that also type the Model within the View so it is checked at compile time. This eliminates the need for magic strings (second part of your question).
In MVVM you have Model View ViewModel. This is a way of binding a ViewModel directly to the UI layer via a View which is used a lot in WPF. It replaces the need for a controller and it's generally a 1-to-1 mapping with the UI. It's just an alternative mechanism that solves the same problem (of abstraction and seperation of concerns) but better suited to the technology.
Theres some useful info here which might help understand the difference.
Best approach to use strongly typed views
Models:
public class ContentPage
{
public string Title { get; set; }
public string Description { get; set; }
}
public class ContentPagesModel
{
public ContentPage GetAboutPage()
{
var page = new ContentPage();
page.Title = "About us";
page.Description = "This page introduces us";
return page;
}
}
Controller:
public ActionResult About()
{
var model = new ContentPagesModel();
var page = model.GetAboutPage();
return View(page);
}
View:
#model Experiments.AspNetMvc3NewFeatures.Razor.Models.ContentPage
#{
View.Title = Model.Title;
}
<h2>About</h2>
<p>
#Model.Description
</p>
for more detail check out here
I case of using string as keys of ViewData - yes, it will be a lot of exceptions if someone will refactor code.
It sounds like your understanding of MVC is very old, probably based on MVC 1. Things have changed tremendously in the last couple of years.
Now we have strongly typed view models, and we have the ability to use expressions in the view, which by default aren't compile-time validated, but they can be for debug purposes (though it slows down the build a great deal).
What's more, we don't pass model data through ViewDate anymore (well, not directly anyways.. it's still passed that way but the framework hides it).

MVC2 - Possible to invoke action from a DIFFERENT controller?

In a controller, is it possible to return the view of an action from ANOTHER controller? The other option is to return a partial view, which uses Html.Action(...) to return the view from the other controller, but I was wondering if there's anything cleaner. Thanks.
If it's just the view you want to reuse, you can pass in the path to the view. For example:
public ActionResult MyAction()
{
// do your model magic here
return View( "~/Views/OtherController/View.aspx", model );
}
Or you can move the view to Views/Shared like Kyle already suggested.
Yes, if that view is a Shared view. Place the view in the Views/Shared folder in your MVC Project, then both controllers will be able to return it.
If you want to invoke an action on another controller, you can use Controller.RedirectToAction() and pass in the action and controller name.
However, this adds an additional server round trip. If you want to avoid that, you can use the TransferResult class shown here:
How to simulate Server.Transfer in ASP.NET MVC?
I ended up using my original solution, which was having a shared view that invokes an action. It was much less code than I needed. Thanks.
Here's a strategy I use to invoke another action without having to create a special view just for that purpose:
Create a shared view which takes a model that defines an Action, Controller, and RouteValues, and whose sole responsibility is to call RenderAction with the values on that model.
Next create a helper method on your base controller class that takes an Action, Controller, and RouteValues as parameters, and returns the ViewResult for this shared view. That way, you can reuse this helper method and shared view on all your controllers any time you want to render some other action from another action's context.
Of course, if it's just the view and not the action that you want to invoke, Marnix's answer is correct.

ASP.NET MVC3 - How to serve View() from another controller

So in order accomplish what I asked in this post I did the following:
[iPhone]
[ActionName("Index")]
public ActionResult IndexIPhone()
{
return new Test.Areas.Mobile.Controllers.HomeController().Index();
}
[ActionName("Index")]
public ActionResult Index()
{
return View();
}
Which still serves the same view as the Index action method in this controller. Even though I can see it executing the Test.Areas.Mobile.Controllers.HomeController().Index() action method just fine. What's going on here? And how do I serve the Index view from Mobile area without changing the request URL (as asked in the original post referenced above)?
You have a few options:
Redirect to the Action you'd like to return: return RedirectToAction("Action-I-Want").
Return the View by name: return View("The-View-I-Want").
Note that with the 2nd approach you'd have to put your view in the "Shared" folder for all controllers to be able to find it and return it. This can get messy if you end up putting all your views there.
As a side note: The reason your work doesn't find the view is because default view engine looks for the view in the folder that "belongs" to the current executing controller context, regardless of what code you're calling.
Edit:
It is possible to group all "mobile" views in the same folder. On your Global.asax (or where ever you're setting up your ViewEngine, just add the path to your mobile View in the AreaViewLocationFormats. Mind you, you'll still have to name your views differently.
You can also write your own view engine. I'd do something like detecting the browser and then serving the right file. You could setup a convention like View.aspx, and View.m.aspx.
Anyhow, just take a look at WebFormViewEngine and you'll figure out what works best for you.
The easiest way to send a request to a view handled by another controller is RedirectToAction("View-Name", "Controller-Name").
There are overloads of View() that take route information that might work as well, but they'd require more effort to set up.
Well actually the easiest way is to make one version of your site programmed on standards instead of browser detection :D -- however in direct response to accomplish what it in a more of a ASP.NET mvc fashion, using:
RedirectToAction("ViewName", "ControllerName");
is a good method however I have found it is more practical if you feel you must program for different browser standards to create a primary view and an alternate "mobile" view under your controllers views. Then instead of writing special code on every controller, instead extend the controller like so.
public class ControllerExtended : Controller
{
private bool IsMobile = false;
private void DetectMobileDevices(){ .... }
}
Then modify your controller classes to instead say ControllerExtended classes and just add the one line to the top of each Action that you have alternate views of like so:
public class ApplicationsController : ControllerExtended
{
// GET: /Applications/Index
public ActionResult Index() {
this.DetectMobileDevices();
if(this.IsMobile){
return RedirectToAction("MobileIndex");
} else {
// actual action code goes here
return View();
}
}
}
Alternately you can use return View("ViewName"); but from my experience you want to actually perform different actions as opposed to just showing the result in a different view as in the case of presenting an HTML table as opposed to a Flex table to help iPhone users since there is no flash support in the iPhone, etc. (as of this writing)

Categories