Is there a way to access a custom helper from a controller? - c#

I am building this MVC3 Razor ASP.NET application in which I am using a custom WebViewPage that inherits from that. So all my views use my custom WebViewPage:
public class MyCustomWebViewPage : System.Web.Mvc.WebViewPage
{
public CustomHelper MyHelper { get; private set; }
:
}
And then in the web.config located in the Views folder I indicated that MyCustomViewPage is my default base page.
<system.web.webPages.razor>
<pages pageBaseType="Namespace.MyCustomWebViewPage">
<namespaces>
:
</namespaces>
</pages>
</system.web.webPages.razor>
So far so good, on the View's .cshtml pages I can access my custom helper from the razor markup:
#MyHelper.SomeMethod()
Now, there is something I need to do conditionally on the helper. I tried setting the helper's property in the #{} startup section of the CSHTML but that is too late for it to be taken into account.
So, I am thinking of doing it either on the invoked Action method, or by overriding the OnActionExecuting() method.
The problem I have is that in the controller I have not found out a way to access my custom helper such as:
public class AnyController : Controller {
public ActionResult Index() {
MyHelper.SomeProperty = true;
}
}
To explain it a bit more, the concept is that in an action (the action code in the controller) I want to set a property in the custom helper that would enable certain functionality. This way this is not rendered on views that do not need it. It is different from the concept of #section in which the content is defined in the view, rather it says "I want this feature added to the layout" and the layout would output some predefined markup that would enable that feature AND that in several places of the view there might be "nuggets" that require that "feature support markup or script". If I use a section, then that "common" script would appear multiple times which is not what I need.
Having said that, How can I access a (custom) helper defined in the base view page from within a controller?

Consider passing the information through model instead as it is more in-line with MVC. You can either use strongly typed Model or weakly typed ViewBag (sample - Passing data from controller to view).
Controller is not expected to know what exact page will be used to render a view. It just says something "find a view with this name and render this data (model)". There could be different views matching the same name depending on different conditions, so normally you don't have a way to set properties of view object from controller.
Edit: Why it is technically very hard:
Lifetime of an action method call (ActionResult Index(){...}) does not intersect with lifetime of the page (WebViewPage object). WebViewPage object created after action is completed execution. So if you really interested in pushing data from action directly into a view object (not through built in mechanisms) you will need to figure out how to pass some chunk of code (likely in form of delegate) to view creation code.

Related

further clarification about passing data from controller to layout sub view in asp.net mvc 5

I guess I have a pretty standard problem as I want to pass certain data from an asp.net MVC 5 controller to a shared view, in my case navigation.
I have a template that shows username and userpicture in the navigation shared view, so I need to pass the respective data to it.
My layout structure:
Shared\Layout
Shared\Header
Shared\Navigation
%Body%
Shared\Footer
Where the Layout ist the master view and of course I also have my other views like Home\Index.
As you can imagine, I have to show the username and his userpicture in every view except the login/registration or any error views.
These views do not use the layout at all, so everytime a view is rendered using the layout structure, the user is already logged in.
So I was researching about ways to pass data from my controller to the navigation view, although my controller returns the Index view and would appreciate some clarification on their disadvantages and valid choices in my use case:
Use case:
My project has a pretty dumb MVC application that the user can access. Once he logs into the MVC app authenticates the user against the same webapi where it get's it's data from and stores the access token as well as other user details for further requests. I'm not yet sure where to store that data. As far as I understand it, the options would be Cookies, Session and local storage.
As I am pretty new to asp.net, MVC and C# in general, I didn't yet figure out how to make the [Authorize] Attribute work inside the MVC app so it can mark the user as authenticated :/
I guess the key problem is that the MVC app does not have access to the database and therefore cannot check the login and populate the User Identity.
How to transfer data from controller to view:
ViewBag: The easiest way of passing data to the view. It is not strongly typed and can be accessed in all views. I was told it is kind of a bad practise to use it and was advised to use viewModels.
ViewData: seems to be kind of the same thing as viewdata.
ViewModel: A strongly typed model that is passed to the view and needs to be declared in any view that uses it. So if I want to use it in my navigation view, I'd need to declare it there. The big disadvantage of this approach is that every viewmodel needs to have kind of a baseViewModel so they have a common structure which appearently can cause problems later down the road and also prevents me from inheriting other models to populate my viewModelStructure.
Cookies: Obviously I can store data in cookies during login and then access them in the view, but the cookies HAVE to be there so I would not be able to save this information in the session or local storage
Session: I can also store data in the session, but the session expires when the user closes the browser tab.
LocalStorage: This is pretty new to me so I can't judge it.
User Identity: I just discovered that I can also access the user's identity from Context.User.Identity.
Global Filter like the [Authorize] attribute or a custom one: If I understand it correctly, with a global filter I can populate needed data automatically in every controller action and exclude the ones that dont need it like Login/register etc. I'm not yet sure how to apply this way because of my project structure (see above).
RenderAction: I could also call another controller method via the RenderAction helper method to always render that section of the page.
How do you guys solve this problem?
If you need more clarification, please do ask.
Thanks :)
Use a base ViewModel. You can still use inheritance to build up functionality in your view models, they'll all just share a common base view model, as you said.
I'm not sure what problems you envisage with this approach, but I would suggest they're outweighed by the benefit of strongly typed, maintainable view models, that can be used by all your views, including partial views and _Layout.
A suggestion to get you started based on your layout structure:
public abstract class ViewModelBase {
public HeaderViewModel Header {get;}
public NavigationViewModel Navigation {get;}
public FooterViewModel Footer {get;}
public ViewModelBase(HeaderViewModel header, NavigationViewModel navigation, FooterViewModel footer) {
Header = header;
Navigation = navigation;
Footer = footer;
}
}
public class HeaderViewModel {
// properties
public HeaderViewModel(...) {
}
}
public class NavigationViewModel {
// properties
public NavigationViewModel(...) {
}
}
public class FooterViewModel {
// properties
public FooterViewModel(...) {
}
}

MVC Core 2.0 HTML.Action vs Component.InvokeAsync

I am really struggling with changing my mindset from the #html.Action way of doing things to this new Component.Invoke way of life.
So to outline my structure. I have a Controller called "Contract" with the standard Index IActionResult.
In there I have a partial view that is a form, which I would have normally have just loaded from the index.cshtml using the following:
if(Something = my value){
#Html.Action("p_NewContract", new { id = x })
}
Which always worked.
For what I can read, I now need to make a new folder outside my nicely structured view and partial view folders under each Controller. The folder I HAVE to create is "Shared/Components/p_NewContract.cshtml"
And then in that folder, I need to create a .cs file with the postfix of ViewComponent (p_NewContractViewComponent.cs) And somehow the runtime will know those two things belong together?
In that CS file I then add [ViewComponent(Name = "p_NewContract")] before the public class and then after all that I can add to my Index.cshtml
#await Component.InvokeAsync("p_NewContract", new { id= 2})
I have done all of this, no sign of it working. Am I missing something fundamental, is there a way I can get my old Html.Action back? It was so much easier.
Any assistance to help my frustration would be great!
Caz
There's two parts to a view component: your class that derives from ViewComponent and a view that component should return. By convention, your view component classes actually go in /ViewComponents, while again by convention, your view goes into Views/Shared/Components/{component name}/Default.cshtml.
That's just convention, though. The same as how Razor automatically finds views based on controller/action name. In truth, the classes can go wherever you like. They are brought in via reflection, so as long as it's somewhere in the assembly or a referenced assembly, it will be found. The views likewise can be moved wherever you like, you just have to tell it where to find them, since it won't be able to do it automatically based on convention anymore. See: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components#view-search-path

Calling RenderAction and Partial from class Inheriting from WebViewPage<T>

I have MVC 5 project where I have a bunch of views, and I've set it in the web.config in the folder to have them all inherit from a custom class which inherits from WebViewPage so that I can add in properties/methods that all views can use.
public abstract class SCView<T> : WebViewPage<T>
{
}
I happen to have a little bit of presentation logic (yes, I know, not good - but I have to do this, for a lot of different reasons). The logic is simple enough, and goes as follows:
if([condition])
{
Html.RenderAction("DoThis", "Page", new { Area = "Common", model = Model, viewname = "TitlePanel" });
}
else if([another condition])
{
Html.Partial("/Areas/Common/Views/Media/TitlePanelEdit.cshtml", Model) *#
}
Essentially, if the first condition is met, I need to display a certain controller action - else, render a different partial view.
I would like to not do this in every view - so I was hoping to incorporate this logic in the CustomView class somehow. However, I'm finding that the Html helper methods cannot be used directly in this class - if I was inside a controller, this would be easier, obviously. Is there anyway I can put this so that this logic can be centralized?
Maybe I am missing something in your question, however, it seems to me that you just need to standardise your calls.
either pick
#Html.Action
#Html.Partial
Or
#Html.RenderAction
#Html.RenderPartial
Rather than mixing the types, then your viewbase logic will be able to return the same type, either void in the case of Render... as those output to the stream, or an mvchtmlstring if you go with the non render versions

What is the best way in MVC to share a Layout object with between multiple HTML Helpers

I am new to .NET MVC and am looking for a code or non-code answer to a this question. My web application will have hundreds of controllers, partial controllers, etc. All the controllers will share a common layout. I am currently adding a Menu object to the ViewBag from the page's controller.
I'd like to know what is best practice for re-using an object multiple times in an MVC Layout view (similar to the Menu object in my example, below)?
#Html.Header(ViewBag.Menu as IR.Web.Portal.Models.Navigation.Menu)
#Html.SideNav(ViewBag.Menu as IR.Web.Portal.Models.Navigation.Menu)
#Html.Footer(ViewBag.Menu as IR.Web.Portal.Models.Navigation.Menu)
I want to initialize the object only once but use it many times.
I do not want to add code to every controller to make it work. Inheritance a base controller might be a good solution.
The Menu object does not need to be in the ViewBag as it is in my example.
The Menu object will be different for every user of the site. So, Static or Singleton classes are probably out of consideration.
Authentication and Logging are good fits for a Controller Base. However, I think it is a good idea to create a Custom PageBaseType as well. There are many things a Page should do but not a controller... Theme, Logo, Layout, Load Scripts, Possibly Get Menus, etc.
Add the following to the \Views\Web.config:
<system.web.webPages.razor>
<pages pageBaseType="MyPageBase">
</system.web.webPages.razor>
Create a class:
public abstract class MyPageBase<T> : WebViewPage<T>
{
protected override void InitializePage()
{
ViewBag.Menu = new Menu();
base.InitializePage();
}
}

Render partial view from controller

I have a controller ItemsController that has an instance method GetRecent() which returns IQueryable<Item>
In the index page of my application I want to render a partial view _Recent.cshtml using the results from ItemsController.GetRecent()
To do this, I've written the following in my Index.cshtml
I've added namespace references to the ~/Views/web.config so I don't need to write out the controllers full namespace
#{
ViewBag.ItemsController = new ItemsController();
}
...
#Html.Partial("~/Views/Items/_Recent.cshtml",
((ItemsController)ViewBag.ItemsController).GetRecentItems())
I thought of passing new ItemsController().GetRecentItems() directly or turning GetRecentItems() into a static method however I'm not sure what direction this should take.
I want to know if this is an accepted way of building a partial view from a controller and if not, how can this be accomplished more efficiently?
RenderAction
The first option is Html.Action or Html.RenderAction(What's the difference?). These are part of the ASP.Net MVC framework, and provide a more polished means to implement something similar to your code sample.
With this approach, your view calls the appropriate controller, which fetches any needed data, renders the partial view, and returns it.
I tend to shy away from using this approach as it seems backwards, i.e. the view "knows" quite a bit about controllers and controller structure and is actively executing rather than just consuming.
Still, it's a legitimate option.
Remodeling
You could pass the "recent items" along with the view model for the main view. This is more effort to model, but "pure" in that your view is less coupled to the controllers. Your partial view would not need to change.
Async Rendering
You could render the recent items using an AJAX call to the appropriate controller, and have that controller return the partial view as its result. This is probably the most effort, but elegant in that some of the work is delayed until the rest of the page is rendered (possibly allowing lazy loading, and/or improved page load times).
Done correctly, this approach also allows decent separation of concerns.
Related Question (not identical, as it doesn't focus on passing data to the partial view)

Categories

Resources