AI have a web application developed using ASP.NET MVC which has a particular url, i.e, https://(myapp.com//ProductionLogs/GetTop25Logs which can be accessed by users without even logging in. I have two layouts 1.VANILLA and 2.CHOCO designed using Razor. I would like to display the VANILLA layout if a user who has not logged in, types the URL mentioned above and display the CHOCO layout for the user who had logged in.
But I am unable to achieve this. All the time, CHOCO layout is getting displayed Here's the code :
// Inside LogsController.cs
public action GetTop25Logs()
{
bool isPlainLayout = IfNotLoggedInShowVanillaLayout();
ViewBag.PLAINLAYOUT = isPlainLayout;
return View("Top25Logs");
}
public bool IfNotLoggedInShowVanillaLayout()
{
return !Request.IsAuthenticated;
}
//Inside Top25Logs.cshtml
#{
if (ViewBag.PLAINLAYOUT)
{ Layout = "~/Views/Shared/_LayoutVANILLA.cshtml";}
else
{ Layout = "~/Views/Shared/_LayoutCHOCO.cshtml";}
}
Related
This is one of method inside my controller.
[HttpPost]
public JsonResult Ajax(string AJAXParameter1)
{
List<string> data = new List<string>();
data.Add("AJAXParameter1=" + AJAXParameter1);
return Json(new { data, result = "OK" });
}
If parameter AJAXParameter1 recieves value as passport i want to display view that contains fileds related to passport. If it recieves value as pan i want to render some different view that allow us to add pan card related details. For pan and passport i have models. Based on Id i want to generate view.
I believe the best way to do this is to simply create two views or partialviews based on how your application will have to look, one for the passport and one for the pan card.
Then in your controller method see which one you receive and return the view needed.
something like:
[HttpPost] public ActionResult ReturnNeeded(string Parameter1){
if (IsPassport(Parameter1)){
return View("PasswordView");
}else if (IsPan(Parameter1)){
return View("PanView");
}
return View("OopsBadRequest");
}
maybe?
I have a _Layout.cshtml page which will render every time when a view is loaded. I have a session variable which stores the current user. While loading a view I need to check whether that session is out. If session out I need to render another view to prompt the user to login again.
I have written this code in the _Layout.cshtml
#if( #Session["UserId"]!=null)
{/* Header,RenderBoady(),footer etc of layout goes here */}
else
{/*call another view here*/}
I dont know what have to write on else part.
Most likely, you need redirect user rather than vary layout. I suggest to use filters for this task and may think of 2 solutions.
First, utilize built-in [Authorize] attribute to reduce amount of custom logic.
Second, use custom AuthorizeFilter, which may looks like one below:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
Controller controller = filterContext.Controller as Controller;
if (IsSessionExpired(filterContext))
{
filterContext.Result = new RedirectResult(<where to redirect>);
}
}
private bool IsSessionExpired(AuthorizationContext filterContext)
{
<logic>
}
}
Pick any you like and register as global filter or mark specific contollers/actions.
Use asp.net mvc authorze filter attribute for user authentication,
Enable forms authentication in web.config
Use _ViewStart.cshtml and perform this check in that file. Based on status set the layout page for logged in user and logged out users
#{
if( #Session["UserId"]!=null)
{
Layout = "~/Views/Shared/_Layout.cshtml";
}
else
{
Layout = "~/Views/Shared/_LayoutPartial.cshtml";
}
}
Say the requirement is to redirect to another page after successfully saving a model in an ASP.NET MVC controller:
[HttpPost]
public ActionResult Index(ViewModel viewModel)
{
if (ModelState.IsValid)
{
// Do save;
return RedirectToAction("whatever"); // <--- here's the problem
}
// display validation errors and so
return View(viewModel);
}
This would work fine unless the controller was rendered as a Child Action:
#{
Layout = "my layout";
Html.RenderAction("something else, a view for example");
Html.RenderAction("Index action of the above controller"); // <----
}
In this case RedirectResult class would check and see that the context is a child action and would throw the exception: "Child actions are not allowed to perform redirect actions".
I understand that writing to Response stream is already in progress and a redirect cannot take place here but nevertheless one has to be able to redirect to other pages after a server-side action in a controller even if that action is a child action. We use Layouts and RenderActions to reuse the shared parts of the page design.
How would you implement such a redirect in such a controller?
Edit:
The main goal is to reuse View/Controllers that do a specific job and split them to logical concepts like displaying some data or providing an edit form. My approach here is to use RenderAction to render their result into a container. Container view (main page's Index action) acts as a traditional asp.net Page, its Layout view as a Master page and edit and view controller/views are equivalent to User Controls (modules). The problem is there is no way to Redirect the Response after something has been written to it:
I'll submit a new answer in an attempt to keep things clean.
A normal mvc flow goes like this :
Http command reaches 1 controller which acts as a choirmaster(aka controller) and calls several logic containers(e.g services / commandHandlers) :
public ActionResult Index(){
var data = _yourService.FetchData();
return View(data);
}
This controller renders 1 view which can have multiple partials
#{
Layout = "my layout";
}
<p>Some html</p>
Html.RenderPartial("A shared partial");
Html.RenderPartial("shared\yourUserControl", Model.PropertyOrSomething);
If the partial contains too much logic to generate you could add a RenderAction or create an htmlHelper extension.
But neither of these should be able to control the flow of your request, a save or something that can redirect should in my opinion never be called from inside a view.
I assume you want to reuse the code in your controller so badly because it is getting quite big.
My advice would be to try and clear that controller up as much as possible by delegating as much of the logic upwards as you can.
Just by looking at your controller method for 5 seconds you should get an idea of what this action will do, if that's not the case : refactor it ! :)
If i understand correctly you have a parent controller which accepts a saveAction and while rendering the view you call another(?) saveAction as a child.
This flow feels unnatural to me. The parent action and only the parent action should handle the save command. You can render as many child actions as you want as long as they are only used to render some html(as this is what the view does). Don't let them handle redirection or saving, that way of working is everything but transparant for colleges or future you.
The controller controls the flow, not the view.
Edit:
A normal setup would be having 2 actions, eg: Index and Put.
public ActionResult Index(){
//fill model with dropdown data etc
return View();
}
public ActionResult Put(viewModel data){
if (ModelState.IsValid)
{
// Do save;
return RedirectToAction("whatever"); // <--- here's the problem
}
// display validation errors and so
return View("Index",viewModel);
}
Edit2:
If you return View("Index",viewModel) you will generate your index view with it's layout and the validation messages will be located in the modelstate.
Your view however should only have 1 childaction(or more if there are multiple, as long as it's not the save action).
Your Index view could look like this :
#{
Layout = "my layout";
}
Html.RenderAction("something else, a view for example");
#Html.BeginForm("Put","YourController"){
//all your input controls which will also show the validation errors
}
Edit 3:
If you want to reuse html code you should use #Html.Partial or Html helper extension methods. Note that if you pass no model the parents model is passed but you can pass a submodel to match the type safety of the partial.
It would look something like this :
#{
Layout = "my layout";
}
Html.RenderAction("something else, a view for example");
Html.RenderPartial("shared\yourUserControl", Model.PropertyOrSomething);
Try using AJAX in your view to do this, and instead of returning a RedirectToAction in your controller, return a JSON object:
View:
$.ajax({
url: '<%: Html.ResolveUrl("~/ControllerFolder/ControllerName/") %>',
type: "POST",
data: data,
success: function (result) {
$("#Div").html(result);
if (result.redirectUrl != null)
{
window.location = result.redirectUrl;
}
}
});
Controller:
[HttpPost]
public ActionResult Index(ViewModel viewModel)
{
if (ModelState.IsValid)
{
// Do save;
return Json(new { redirectUrl = Url.Action("NewAction", "NewController", RouteValues)});
}
// display validation errors and so
return View(viewModel);
}
Hope this helps...
While reviewing some code in grails, I noticed that at a certain point specific code is reused in every controller and passed to the views to render a view section which can be normally be part of the main layout. The only problem is how to pass those values to the main layout from another point outside the specific controllers.
Any best-practice in dealing with similar cases of passing variables to the main layout?
You use a Filter for this:
For example:
class MyFilters {
def filters = {
all(controller:'*', action:'*') {
before = { }
after = { Map model ->
// add your common data here
model.commonData = ...
return true
}
afterView = { Exception e -> }
}
}
}
The after closure gets called after the controller execution but before the view is rendered. You can also be more specific on the filter configuration (all(controller:'*', action:'*')). For example you can to exclude certain controllers or something like that.
In Razor, when loading a partial view, it is possible to just specify the partial view name, and the Razor view engine will search the RazorViewEngine.PartialViewLocationFormats:
#Html.RenderPartial("_PartialView", Model);
will actually search the locations specified in PartialViewLocationFormats in the view engine, such as for example
~/Views/Home/_PartialView.cshtml
~/Views/Shared/_PartialView.cshtml
However, when specifying the Layout, I seem to be forced to specify a specific path to the layout:
#Layout = "~/Views/Shared/MyLayout.cshtml";
What I would like to do would be to specify the layout just by name, and have the the actual layout be found by searching a list of common locations:
#Layout = "MyLayout";
...but I can't find any facilities to do so. Since I could not find any documentation regarding this, I tried playing with setting RazorViewEngine.MasterLocationFormats, but this property is not used when locating layouts.
Does anybody know how to do this?
Recently I was struggling on a similar issue where, in a themed application that uses a custom ViewEngine to search theme's location first for views, I was trying to override some master files. One way to force the location of the layout to go through the ViewEngine's FindView is to specify the name of the master view in a controller action when returning:
return View("myView", "myViewMaster");
However, if "myViewMaster" also has a layout (nested layouts), this method doesn't work for the nested layout. My best solution so far is to call the view engine directly in the view:
#{
Layout = (ViewEngines.Engines.FindView(this.ViewContext.Controller.ControllerContext, "myViewMaster", "").View as RazorView).ViewPath;
}
This works but could certainly be encapsulated in a extension method to avoid repetition and abstract the code.
Hope it helps!
I tried a couple of different things and couldn't find a way to do what you wanted. However, you could just leave the Layout blank and use the _ViewStart.cshtml file to assign the layout for each page using a ViewBag property is something.
So if you had an enum (for instance) and you set the dynamic viewbag property of CustomLayout to a value of that enum, you could use the _ViewStart file to do something like this.
#{
MvcApplication12.Controllers.CustomLayouts customLayout = this.ViewContext.Controller.ViewBag.CustomLayout;
switch (customLayout)
{
case MvcApplication12.Controllers.CustomLayouts.Blue: Layout = "~/Views/Shared/Blue.cshtml"; break;
case MvcApplication12.Controllers.CustomLayouts.Green: Layout = "~/Views/Shared/Green.cshtml"; break;
case MvcApplication12.Controllers.CustomLayouts.Default:
default: Layout = "~/Views/Shared/Default.cshtml"; break;
}
}
Not sure if this helps or not.
I took the accepted answer from #sowee15 and added an extension method to help wrap it up a bit:
using System;
using System.Web.Mvc;
namespace MyApp.Classes
{
public static class ViewEngineCollectionExtensions
{
public static string FindViewPath(this ViewEngineCollection viewEngines, ControllerContext controllerContext, string viewName, string masterName = null)
{
var viewResult = ViewEngines.Engines.FindView(controllerContext, viewName, masterName ?? string.Empty);
if(viewResult == null)
throw new Exception(string.Format("The specified view {0} could not be found.", viewName));
var view = viewResult.View as RazorView;
if(viewResult == null)
throw new Exception(string.Format("The specified view {0} must be a Razor view.", viewName));
return view.ViewPath;
}
}
}
Then you can call extension from your view or viewstart.cshtml like so:
#{
Layout = ViewEngines.Engines.FindViewPath(ViewContext.Controller.ControllerContext, "_PopupLayout");
}