ASP.Net MVC Controller for _Layout - asp.net-mvc

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.

Related

EntityFramework SaveChanges() throws concurrent transaction error

In my MVC application, I have a page that loads a record from my POLICIES table and then uses it in my View. My View then has the data from this record displayed on the page, however in order to edit the record data the user needs to click the "Edit Policy" button, which launches a jQuery UI dialog with the same record in EDIT mode. I realize I could just allow them to edit it from the main View, however that is not what my client wants.
The trouble I'm having is, when I'm in my jQuery UI Dialog, I get the error below when I try to save the record.
FirebirdSql.Data.FirebirdClient.FbException: lock conflict on no wait
transaction
The Controller method for my dialog executes the following code. The PolicyModel is simply a class which serves as the ViewModel for the dialog, and the Policy property is an object representing the Policy table.
public ActionResult Policy(int policyNo) {
PolicyModel policyModel = new PolicyModel();
policyModel.Policy = dbContext.POLICIES.FirstOrDefault(db => db.POLICY_NO == policyNo);
return View(policyModel);
}
In the "Policy" View, I do a standard form using:
#using (Html.BeingForm("SavePolicy", "MyController", FormMethod.Post)) {
//hidden element for policyNo created with #Html.HiddenFor
//form elements here created using the #Html.TextBoxFor..etc.
}
The dialog button to save simply creates new FormData with var formData = new FormData($('#myformid').get(0)); I then pass that to my save controller method.
The Save method is set up like the following
public ActionResult SavePolicy(PolicyModel policyModel) {
var policy = dbContext.POLICIES.FirstOrDefault(db => db.POLICY_NO == policyModel.POLICY_NO);
if (TryUpdateModel(policy,"Policy", updateFields.ToArray())) {
dbContext.Entry(policy).State = EntityState.Modified;
dbContext.SaveChanges();
}
return Json( new { result = 1, JsonRequestBehavior.AllowGet } );
}
If I change the POLICY_NO manually though to ANY other policy number than the one currently active in the dialog like this...
var policy = dbContext.POLICIES.FirstOrDefault(db => db.POLICY_NO == 12345);
The save will update correctly.
It's like the dialog View is holding onto the resource or something. any ideas?
UPDATE
For reference, the dbContext is scoped to the Controller class in which my SavePolicy method lives as seen below...
public class MainController : Controller {
private DBModel dbContext = new DBModel();
// other methods
public ActionResult SavePolicy(PolicyModel policyModel) {
// method code as see above
}
}
ASP.NET MVC Controllers usually have this:
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
So, if you are declaring your context outside of your action, you should verify if this method is implemented.
Turns out that, at the first execution (a select), your context keeps track of the record at Firebird and it is never disposed. The second execution will try to select the same entry again, which is still tracked by another context that was not disposed properly.
Using the scoped context inside each action is another way to solve, but it is a bit more cumbersome in my standpoint.

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.

What happens when you return View when should be PartialView?

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

Reusing controllers

Is it possible to re-use controllers in ASP MVC? And if so how?
Perhaps re-use isn't the right word. The situation is I have a menu and sub menu navigation bars as shown below (actually there is another nav bar what is shown)- I know the colour scheme needs some work
The upper bar is populated from a database, so there could be more or less than 3 plans.
The lower bar always has the same three entries. The views for each of these entries are the same regardless of which plan is selected, though they are different from each other. Obviously the data within them is different (populated from different tables).
That is Plan A -> Suggested Points view is the same as Plan B -> Suggested Points view.
But Plan A -> Suggested Points view is not same as Plan A -> Accepted Points view
In order to do this with the views I intend to use partial views, so the same view files can be re-used.
However, how can I do the equivalent for the controllers?
What I would like if for url paths such as:
/PlanA/SuggestedPoints
/PlanB/SuggestedPoints
To my mind I just want the Plan links to set a variable that tells the Points views which database they should hook up to. Which may be the wrong way to think of it and I suspect is incompatible with the url path suggestion above.
Suggested Approach
I would suggest it is better to include a controller name in the route, that way you won't get conflicts so easily with other controllers in your app.
You can modify your RouteConfig.cs file and map a new route. Make sure to add the custom route before the "Default" one.
Something like this:
routes.MapRoute(
"Plans",
"Plans/{planName}/{action}",
new { controller = "Plans", action = "Index" }
);
// Default route here.
Then you would have a controller called Plans with each of your actions having a parameter called planName that lets you identify with plan to work with...
public class PlansController : Controller
{
public ActionResult SuggestedPoints(string planName)
{
// create your view here, using the planName to get the correct data.
}
public ActionResult AcceptedPoints(string planName)
{
// create your view here, using the planName to get the correct data.
}
// etc.
}
This method will allow URL's in the following format:
/Plans/PlanA/SuggestedPoints, /Plans/PlanA/SuggestedPoints, /Plans/PlanB/AcceptedPoints, /Plans/PlanB/AcceptedPoints, etc.
NOTE: If your plans are in your database, it may be more beneficial to use an ID for the plan, but the URL's would look less friendly so that is up to you.
Finally, when you want to create your links in your view, you can use the following:
#Html.RouteLink("link text", "SuggestedPoints", new { controller = "Plans", planName = "PlanA" })
Your Exact Request
If you absolutely must use the URL formats you suggested, then you can do the following which requires a route for each action, but be wary that you will need to rely on the uniqueness of the Action names to ensure they don't conflict with other controllers...
routes.MapRoute(
"SuggestedPoints",
"{planName}/SuggestedPoints",
new { controller = "Plans", action = "SuggestedPoints" }
);
routes.MapRoute(
"AcceptedPoints",
"{planName}/AcceptedPoints",
new { controller = "Plans", action = "AcceptedPoints" }
);
routes.MapRoute(
"RejectedPoints",
"{planName}/RejectedPoints",
new { controller = "Plans", action = "RejectedPoints" }
);
// Default route here.
In this instance, the controller will remain the same as my first suggestion above. Which will allows URL's like as follows:
/PlanA/SuggestedPoints, /PlanA/SuggestedPoints, /PlanB/AcceptedPoints, /PlanB/AcceptedPoints, etc.
It can be something like this:
public class PlanController
{
public ActionResult SuggestedPoints(string plan) //or int planID
{
return View();
}
public ActionResult AcceptedPoints(string plan) //or int planID
{
return View();
}
public ActionResult RejectedPoints(string plan) //or int planID
{
return View();
}
}
and example urls next:
/Plan/SuggestedPoints/PlanA
/Plan/AcceptedPoints/PlanB

How can you use session variable to determine view?

I have my global.asax setup with the following Session_Start:
protected void Session_Start()
{
HttpContext.Current.Session.Add("sourceCode", "default");
}
On my controller I have the following:
public ActionResult Index(string sourceCode)
{
if (sourceCode != null && sourceCode != "default")
{
Session["sourceCode"] = sourceCode;
return View();
}
else
{
return View();
}
}
I want to be able to display different partial layouts based on this session variable. What is the proper way to do this? Can I load a partial view from the controller or do I need to handle that on the view?
This is a variable that I want to use site wide to determine special pricing and landing page creatives. Do I have to set this same structure up on every single controller or is there a more global way of doing this?
Thanks,
Brian
If you want to show the layout in all the pages, you might want to add the logic in the layout file. There, you will add something like that (assuming razor)
#if(HttpContext.Current.Session["someValue"]){
#*render some partial*#
}else{
#*render some other partial*#
}
By the convention of MVC, controller should decide which view it should open. For this in controller you have code like this:
public ActionResult Index(string sourceCode)
{
if (sourceCode != null && sourceCode != "default")
{
Session["sourceCode"] = sourceCode;
ViewData["PartialView"] = "partialviewname1";
}
else
{
ViewData["PartialView"] = "partialviewname2";
}
return View();
}
and in view you can write code something like this:
<div>
#Html.Partial(Convert.ToString(ViewData["PartialView"]))
</div>
and if you have decide which partial view you have to load on each and every request then you can write above logic in global action filter. Global action filter get executed before any requested action method. To know more about global action filter you can explore this link.
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs

Resources