What happens when you return View when should be PartialView? - asp.net-mvc

In a View I am calling an action that returns a View
View:
Html.RenderAction("Read", "Stats", new { Module = statsModel.Module, Name = statsModel.Name });
Controller:
public ActionResult Read(Module module, string name, bool showStatsItems = true)
{
eRPortalEntities db = new eRPortalEntities();
StatsPanelService service = new StatsPanelService(db, UserID);
StatsPanelViewModel spv = service.Read(module, name);
spv.ShowStatsItems = showStatsItems;
return View("StatsPanel", spv);
}
This unfortunately causes some of my Bootstrap functionality to break. Such as dropdowns and modals not toggling.
If instead I have the controller return a PartialView, everything works as expected
return PartialView("StatsPanel", spv);
I'm not looking for a specific reason why my bootstrap stopped working but more of an explanation of... Why would this cause issues in general?

Unless you explicitly specify the Layout to be null, When you do return View("StatsPanel"), Razor view engine will render the view content inside the Layout (_Layout.cshtml) similar to how you render a page normallly. That means, it will include all those scripts & Css in the head section again. That could be the reason it is messing up your markup.
Using PartialView() method seems appropriate in your use case. If you still want to use the View() method, you can explicitly define layout as null in your StatsPanel.cshtml view like this
#{ Layout = null; }

Related

Bind GridLookup control in shared layout mvc 5

I am using Devexpress MVC application where i used one GridLookup control in shared layout. I needed here some controller which will call a method on every request. For this purpose i used base controller and using ActionExecutingContext method where i am calling my menu loading and gridlookup loading. I am using viewdata to set the value and in shared view i used partial view of my GridLookup control where i am binding viewdata to GridLookup.
Below is the Base controller used to load menu and filters of gridlookup.
protected override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
ProcessingMenus();
}
Below is the shared layout where i am using partialview of gridlookup control
#Html.Partial("_ReportFilter")
Below is the GridLookup control used in this partial:
#{
var gridLookup = Html.DevExpress().GridLookup(settings =>
{
settings.Name = "LookupLobMultiple";
settings.KeyFieldName = "Description";
settings.GridViewProperties.CallbackRouteValues = new { Controller = "Manage", Action = "BindLOB" };
settings.Properties.SelectionMode = GridLookupSelectionMode.Multiple;
settings.Properties.TextFormatString = "{0}";
settings.Properties.MultiTextSeparator = ";";
settings.CommandColumn.Visible = true;
settings.CommandColumn.ShowSelectCheckbox = true;
settings.CommandColumn.SelectAllCheckboxMode = GridViewSelectAllCheckBoxMode.AllPages;
settings.GridViewProperties.SettingsPager.Visible = false;
settings.GridViewProperties.Settings.ShowGroupPanel = false;
settings.GridViewProperties.Settings.ShowFilterRow = false;
settings.Columns.Add("ID").Visible = false;
settings.Columns.Add("Description").Caption = "Line of Business";
settings.PreRender = (s, e) =>
{
MVCxGridLookup gl = (MVCxGridLookup)s;
gl.GridView.Selection.SelectRowByKey(ViewData["LOB"]);
};
});
}
#gridLookup.BindList(ViewData["LobModal"]).GetHtml()
In the above GridLookup control you can see am binding data using viewdata which is loading in ProcessingMenus method.
First issue here is in GridLookup i have used controller and action method also but this is not calling when i check and uncheck any value and showing Loading....
Second issue when after sometime if i again hit the url OnActionExecuting method is not calling due to it menus are not loading again.
I found the answer from Devexpress team is to call partial view in shared view use #{Html.RenderAction("action", "controller");} and then in that action call the partial view that need to show in shared layout with passing model data.
and in partial view just bind the grid with the passed model.
That's it.
Thank you for all your suggestions.

Partial View Result

Can I return a view in PartialViewResult() like the following?:
public PartialViewResult EditAdminProfile_Post(int Id, FormCollection formCollection)
{
//if (Session["AdminID"] != null)
//{
Admin admin = new Admin();
admin = db.Admins.Single(m => m.ID == Id);
admin.Name = formCollection[0];
admin.Gender = formCollection[1];
admin.Email = formCollection[2];
admin.ContactNumber = formCollection[3];
admin.AboutMe = formCollection[4];
if (ModelState.IsValid)
{
db.Entry(admin).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
}
return PartialView("AdminProfile");
AdminProfile is not a partial view.
Yes you can. It's not advised though, since your _layout is ignored (which means css or script files defined in the layout, so your styles and functionality may behave differently as you'd expect). Even though you can make it work, you're looking at code smell.
Are you looking to reuse AdminProfile as both a partial and normal view?
Create a partial view AdminProfilePartial with reusable content.
In your AdminProfile view, call AdminProfilePartial inside it (even if that's all you use)
Then when you return the full view:
return View("AdminProfile");
Otherwise partial view:
return PartialView("AdminProfilePartial");
The problem is that you don't pass the model to you partial view, that's why when you call it you get a Null reference
Update your code with this line:
return PartialView("AdminProfile", admin);
PS Instead of using FormCollection it's better to use default model binder and accept the Admin model in your action method

ASP.Net MVC Controller for _Layout

I am working on a dynamic menu system for MVC and just to make it work, I created a partial view for the menu and it works great using the syntax below:
#Html.RenderPartial("_Menu", (Models.Menu)ViewBag.MainMenu)
BUT, to do so, I would have to set the MainMenu and FooterMenu (or any other menu for that matter) in the ViewBag on each Controller and each action. To avoid this, I was wondering if there was a recommended event that I could access the ViewBag globally. If not, does anyone recommend passing the Menu object into a session variable? It doesn't sound right to me, but only thing I can think of right now.
UPDATE:
_Layout.cshtml - I included the new call to Action:
#Html.Action("RenderMenu", "SharedController", new { name = "Main" })
SharedController.cs - Added New Action:
public ActionResult RenderMenu(string name)
{
if (db.Menus.Count<Menu>() > 0 && db.MenuItems.Count<MenuItem>() > 0)
{
Menu menu = db.Menus.Include("MenuItems").Single<Menu>(m => m.Name == name);
return PartialView("_MenuLayout", menu);
}
else
{
return PartialView("_MenuLayout", null);
}
}
And it throws the following exception:
The controller for path '/' was not found or does not implement
IController.
UPDATE 2:
So, the issue is that I referenced the Controller by the full name and you only need the name of the controller minus "Controller". Neat tidbit. So, for my example, this works:
#Html.Action("RenderMenu", "Shared", new { name = "Main" })
set your menu up as an action then call it in your master layout.
use #Html.Action()
the action can return a partial view with your menu code in it.

Are the Drivers and Controllers in Orchard somewhat the same?

I'm creating a new widget in the Orchard CMS. The way I do this is by adding a Route and Controller first and try out the functionality by running the code on an url defined in the Route, like http://localhost:30320/Index
My Routes.cs is set up so it routes this request to the specified controller and stuff is happening on the screen. This all works quite well.
Now that I'm happy with the result I tried placing all of this in a new Widget. For this I've created new Migrations class which sets up the widget. This is fairly straightforward and now the widget has been added to the Homepage layer.
The thing I'm running into is the Controller isn't executed anymore. Not very strange as I haven't set up any routes which specify the Controller should be executed. I'm wondering, should I move the Controller logic to the Driver method, so the View still gets the necessary information?
The driver I've got at the moment looks like this:
public class FrontpageDrivers : ContentPartDriver<FrontpageModelPart>
{
protected override DriverResult Display(FrontpageModelPart part, string displayType, dynamic shapeHelper)
{
//return base.Display(part, displayType, shapeHelper);
if (displayType.StartsWith("Detail"))
return ContentShape("Parts_Index", () => shapeHelper.Parts_Index(
LatestPostCollection: part.LatestPostCollection,
TopRatedPostCollection: part.TopRatedPostCollection,
TotalMonthCollection: part.TotalMonthCollection,
ContentPart: part
));
return null;
}
}
The PartsController method which needs to be executed looks like this:
[HttpGet]
public ActionResult Detail()
{
//Do something to get blogposts
var getter = new GetBlogPost(_blogService, _blogPostService, _votingService);
getter.Initialize();
var latestPosts = getter.GetLatestPosts();
var highestRankedPosts = getter.GetHighestRankedPosts();
var archiveData = getter.GetTotalPostsPerMonth();
var viewModel = new FrontpageModelPart();
viewModel.LatestPostCollection = latestPosts;
viewModel.TopRatedPostCollection = highestRankedPosts;
viewModel.TotalMonthCollection = archiveData;
return View("Index", viewModel);
}
I've tried renaming the method to Index and Detail, both won't do the trick.
The view which is shown is /Views/Parts/Index.cshtml. If I put some static text in the file, I can see this view is being rendered correctly.
So, should I move the Controller logic to the Driver, or am I forgetting something in the setup?
Note: I've got the placement, module, migrations and handler in place already.
Edit:
If I'm using this code, everything works quite well:
protected override DriverResult Display(FrontpageModelPart part, string displayType, dynamic shapeHelper)
{
var controller = new PartsController(Services, _blogService, _blogPostService, _votingService);
part = controller.GetIndexViewModel();
if (displayType.StartsWith("Detail"))
return ContentShape("Parts_Index", () => shapeHelper.Parts_Index(
LatestPostCollection: part.LatestPostCollection,
TopRatedPostCollection: part.TopRatedPostCollection,
TotalMonthCollection: part.TotalMonthCollection,
ContentPart: part
));
return null;
}
Even though this works quite well, it just feels like 'hacking' to me...
Use controllers when you want to take over everything that appears in the Content zone, but return a shape result so that the theme, widgets, etc can still chime in. That's what your controller fails to do.

How to set Razor Layout file just specifying the name?

First a little context. When you call Html.RenderPartial you send the View name, that view will be searched at locations specified by RazorViewEngine.PartialViewLocationFormats:
Html.RenderPartial("Post", item);
When you set the Layout property at Razor page, you canĀ“t just say the name, you need to specify the path. How can I just specify the name?
//Layout = "_Layout.cshtml";
Layout = "_Layout"; //Dont work
I need this because I overrided the RazorViewEngine.MasterLocationFormats.
Currently I am specifying the Master at controller:
return View("Index", "_Layout", model);
This works, but I prefer to do this at View.
There is no direct way to do it,
But we can write an HtmlExtension like "RenderPartial()" which will give complete layout path at runtime.
public static class HtmlExtensions
{
public static string ReadLayoutPath<T>(this HtmlHelper<T> html,string layoutName)
{
string[] layoutLocationFormats = new string[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
foreach (var item in layoutLocationFormats)
{
var controllerName= html.ViewContext.RouteData.Values["Controller"].ToString();
var resolveLayoutUrl = string.Format(item, layoutName, controllerName);
string fullLayoutPath = HostingEnvironment.IsHosted ? HostingEnvironment.MapPath(resolveLayoutUrl) : System.IO.Path.GetFullPath(resolveLayoutUrl);
if (File.Exists(fullLayoutPath))
return resolveLayoutUrl;
}
throw new Exception("Page not found.");
}
}
In the view we can use it as,
#{
Layout = Html.ReadLayoutPath("_Layout");
}
Can I ask why you are doing this or more specifically why are you returning a layout page from a controller? You are missing the point of master pages it seems.
You can't specify just the "name", you need to specify the path of the layout view so that it can in turn be applied to the view are rendering.
Layout = "~/SomeCustomLocation/SomeFolder/_Layout.cshtml"

Resources