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
Related
On the site I'm working on, there are users with different permissions on the site.
Given the schedule ID and employee ID that we're currently looking at, we can get their role-specific permissions.
Right now, our BaseModel has a property that properly accesses the DB and grabs this info.
For all views that pass a model to the view, everything runs fine.
The problem lies in Controller Methods where no model is passed. In a few views, all they're supplied is a few ViewBag entries, and work fine.
However, I /need/ the CurrentPermissions property in those pages nonetheless, for the layout. Whether or not the permissions have one boolean value set true/false, something may/may not be displayed/populated.
So, my option seem to be:
Somehow throw my CurrentPermissions into a ViewBag entry for all views, and access them through that instead of the base model.
I'm not sure how to do this. I've seen people using OnActionExecuting, but that fails since my connection to TransactionManager is not yet set up at that point.
Somehow throw just the BaseModel into those views that don't currently pass a model. I'm refraining from this as much as possible. I'm not sure how I would go about doing such, but it seems like that would over-complicate the situation.
How can I go about pushing this CurrentPermissions object (generated from a call to my TransactionManager) to every view (specifically, the Layouts!)
Your approach is what we use in out projects... and we use this approach to systematically remove the use of ViewBag changing it to ViewModels.
Other approach we have used (for UserPreferences in my case) is adding an ActionFilter that ends including the preference in the ViewBag. You decorate the actions needing it with [IncludePreferences] in my case (that is the name of my filter attribute.
EDIT ActionFilter:
public class IncludePreferencesAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller as BaseController;
// IController is not necessarily a Controller
if (controller != null)
{
//I have my preferences in the BaseController
//and cached but here you can query the DB
controller.ViewBag.MyPreferences = controller.TenantPreferences;
}
}
}
In you action you decorate it using [IncludePreferences]
As a temporary solution, I'm doing the following at the top of my Layout:
#{ OurModel.SupervisorRestriction CurrentSupervisorRestrictions = ViewBag.CurrentSupervisorRestrictions ?? Model.CurrentSupervisorRestrictions; }
This way, if we're passing in an object then it works just fine. Otherwise, I'll directly pass in a ViewBag.CurrentSupervisorRestrictions from the controller. There are only a few cases, so it's not that bad.
Better suggestions would be great, though.
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.
After I found this in our code, I realized a few things:
"get it done now, get it right later" has a limit
I don't actually know where this goes with MVC3
#model int
#using Company.Core.Logic.Financial
#using Company.Core.Repositories
#{
var values = from AccountPlan e in new AccountPlanRepository().GetItemList()
where String.IsNullOrEmpty(e.PromoCode) // filter out promotional plans
select new { id = e.AccountPlanId, name = e.Description };
var items = new SelectList(values, "id", "name", Model);
}
#Html.DropDownListFor(m => m, items)
This specifically is an editor template (#Html.EditorFor(m => m.AccountPlan)), but it made me realize that I don't know where this kind of code goes for common things like menu-builders. If you're using Layouts for MVC3 (and who isn't), where is the code for building a menu across the top based on the user's roles? I'd imagine the view code would be iterating through pre-built menu items and HTML-ifying them, but since the Model is strong-typed, does that mean that all Models need to have knowledge of the menu items?
For once, this is where Webforms made more sense to me, since this would go in the CodeBehind, but I really want to get away from that.
edit: even though I started asking about Layout code, I'm under the assumption it works for EditorTemplates and DisplayTemplates as well. If this is an incorrect assumption, please let me know where these are supposed to go.
edit2: What I ultimately want is to have a clean, possibly even dependency-injectable place to run code that's called from an EditorTemplate. Perhaps this is a case where the EditorTemplate calls immediately into a RenderAction?
Looks like this solves the problem pretty well (see the marked answer, not the original question):
http://forums.asp.net/t/1566328.aspx/1?Building+ASP+NET+MVC+Master+Page+Menu+Dynamically+Based+on+the+current+User+s+Role+s+quot+
Basically, call RenderAction(...) and it will build the model it needs, rather than forcing you to have every model require a list of menu items.
For me personally, I do a lot of menu filtering based off of active directory groups so I need to know what their access levels are across the entire application.
I create a new controller that I call ControllerBase
public class ControllerBase : Controller
{
//authorization group setting an menu creation here.
//set properties and objects to ViewBag items to access from the front end.
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
and then on all the other controllers in my project I just extend from ControllerBase
public class HomeController : ControllerBase
{}
This keeps all my server logic in one file for managing permissions and gives all my pages access to these variables when I need to hide or show different ui elements based on permissions.
Child Actions are perfect for this scenario. The logic required to generate the view is wrapped up in a controller action, like normal, and the view that wants to make use of the child action simply renders the action..
You can also cache these partial views, which would make sense for something like a main menu - as presumably the users permissions won't change that often.
e.g.
[OutputCache(Duration = 300)]
[ChildActionOnly]
public ViewResult MainMenu()
{
var model = GetMenuModel();
return View(model);
}
The view that wants to render the child action does so like this.
#{ Html.RenderAction("MainMenu", "Account"); }
And thus the view calling the ChildAction has no need to know what model the view requires.
There are also overloads on RenderAction, should your child action require you to pass parameters to it.
You shouldn't (have to) access the Repository inside the View. That belongs in the Controller.
And the Menu is implemented in the Masterpage, you don't give much details on specifics.
I have a partial view that contains my form input(textboxes). I have 2 other partial views that use this same form. One for adding a product and one for editing a product.
This form uses a view model(Lets call it CoreViewModel). Now editing product has a couple more fields then adding a product.
I am wondering how can I add these extra fields without them showing up on an add product form?
I cannot add these extra fields to the edit product view they must be in the CoreViewModel otherwise I think styling it will be a nightmare.
I was thinking of having maybe a base class and then for the editing. I would send it a view model that inherits this base class.
Check in the view if the View Model is of this inherited class and not a base class and if it is not a base class render the code.
This way I am not sticking the edit specific code into my CoreViewModel that both the add view and the edit view have access.
I hope this sort of makes sense.
Thanks
Edit
Using Muhammad Adeel Zahid code as I base I think I got it to work
public class CreateViewModel
{
......
......
}
public class EditViewModel:CreateViewModel{
public string AdditionalProperty1{get;set;}
public string AdditionalProperty2{get;set;}
}
Controller
EditViewModel viewModel = new EditViewModel();
// add all properties need
// cast it to base
return PartialView("MyEditView", (CreateViewModel)viewModel);
View 1
#Model CreateViewModel
#using (Html.BeginForm())
{
#Html.Partial("Form", Model)
}
Form View
#Model CreateViewModel
// all properties from CreateView are in here
// try and do a safe case back to an EditViewModel
#{EditViewModel edit = Model as EditViewModel ;}
// if edit is null then must be using this form to create. If it is not null then it is an edit
#if (edit != null)
{ // pass in the view model and in this view all specific controls for the edit view will be generated. You will also have intellisense.
#Html.Partial("EditView",edit)
}
When you post it back to your Edit action result just take in the EditViewModel and cast it back to your base. Then you will have all the properties as it seems to work
I have often read people advising against such things. They often urge having viewmodel per view (even for edit and create view of same entity for that matter). Again, it all comes down to what you are doing. You may have different data annotations on edit and create view for different validation needs but if they are the same, we probably have a point to use same viewmodel for create and edit.
To solve your scenario I can't figure out couple of options. First, keep a boolean property in your view model telling you if it is and edit or create and conditionally render you properties on view
public class MyViewModel
{
public string P1{get;set;}
....
public boolean Editing{get;set;}
}
Set Editing property to false in Create ActionResult and to true in Edit ActionReult. This is the simplest method. The second one is little dirtier, but you will feel like using the technology. You can use dynamic behavior of c# 4.0. have your page inherit from dynamic in iherits directive of the page (I use aspx view engine). Then have a create ViewModel:
public class CreateViewModel
{
......
......
}
and one Edit ViewModel
public class EditViewModel:CreateViewModel{
public string AdditionalProperty1{get;set;}
public string AdditionalProperty2{get;set;}
}
and in your view you can do something like:
<%:if(Model.GetType().Name.ToString() == "EditViewModel"){%>
<%:Html.Textbox("AdditionalProperty1")%>
<%:Html.Textbox("AdditionalProperty1")%>
<%}>
There is a price to pay with dynamic. You lose intellisense and you can't use strongly typed helpers (at least in asp.net MVC 2).
I have created a Base-Controller from which all the controllers inherit. Currently this controller fills some data (which I use in most views) into the ViewData-Container like this:
protected override void Initialize(System.Web.Routing.RequestContext rc)
{
base.Initialize(rc);
ViewData["cms_configuration"] = new CmsConfiguration();
// etc.
}
I don't like the fact that I need to read (and cast) from ViewData within the views. I'd like to introduce a BaseViewModel from which all ViewModels will inherit from, defining the properties instead of using ViewData. But how or where can I populate the BaseViewModel within the BaseController? Is there some kind of hook? Or do I simply need to define a function in BaseController, which I call in the Child-Controller?
E.g. (Child-Controller:
//{...}
base.PopulateBaseView(MyView);
return View(MyView);
Thx for any tipps.
sl3dg3
You could optionally use ActionFilters to do stuff like this:
Check out this article:
http://www.asp.net/mvc/tutorials/understanding-action-filters-cs
It explains ActionFilters nicely. That way you can separate different populate-logic into different filters, and turn them on and off as you please.