So I want to have a status page that will show a different layout of the equipment depending on who's using it which will be a variable defined in the web.config. Should I be creating a separate controller per view? Some of the background functions should be similar but some will probably be different in the future. Or should I have the same cshtml file and hide html markup depending on who's using it or not?
I was thinking of doing something like:
#if(System.Configuration.ConfigurationManager.AppSettings["IsSuperUser"] == "true")
{
Status
}
else {
Status
}
Or is this a bad idea?
There are several options, it all depends on your needs and preferences.
Your code will work, however you must also double check permission in your controller! For example, your url will be "/SuperUser/Status" and "/User/Status". Now, what's stopping non-super user to type in "/SuperUser/Status" to the address bar?
One important rule, never trust the end users! Assume that they will not do what you intend them to do.
Given all, my preference would be to include a variable in your Model to identify the user level (super vs non super), then use that to determine the layout in your views. Remember, you can also change the layout of the view based on variable/expression.
#Layout = Model.IsSuperUser ? "_SuperLayout.cshtml" : "_RegularLayout.cshtml";
Sounds like a view concern. I would pass the config data through a dependency in the controller and render partials:
#if (Model.IsSuperUser)
{
#Html.Partial("_SuperUser")
}
else
{
#Html.Partial("_User")
}
The controller can then do something like:
public ActionResult Index()
{
var vm = new MyViewModel();
vm.IsSuperUser = _config.GetSuperUser();
return View(vm);
}
Related
I have been asked to work on already existing code for a .NET MVC Application, and am required to add a new page. I have done this, but am having issues.
The application has a common layout defined in _Layout.cshtml page (I need to use the same layout for my new page). The path is present in Viewstart.cshtml page.
This is how an existing page works:
The controller has this code:
public ViewResult Index()
{
TransactionHistoryViewModel model = new TransactionHistoryViewModel();
model.SearchType = "TransactionHistory";
return View(model);
}
When the code is run, immediately after executing the above lines, it goes on to the _ViewStart.cstml file and executes the following:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
But when I try to use the same logic for the controller I have created (I have already set up the models):
public ViewResult Index()
{
TransportPlannerViewModel model = new TransportPlannerViewModel();
model.SearchType = "TransportPlanner";
return View(model);
}
the code stops executing after the above block, and does not move on to the _ViewStart file (I figured this out using breakpoints).
I am required by my employers to maintain consistency with the existing code, so I have tried to stick to the original coding style. Could this be an issue with wrong model mapping or setup?
If you have 2x public ViewResult Index() in the same controller, it is not going to work, you will confuse the application about which method signature to choose, because they are identical.
You should have your TransportPlannerViewModel code in a seperate controller. and this should be pointing to a different View Folder associated with that controller.
Ie. If the controller for the first code block is TransactionHistory, then it will be looking for a view under Views\TransactionHistory\Index.cshtml.
By default it uses the pattern Views\ControllerName\ActionName (methodName) to look for the view.
So to get it to work for your other method, if I understand what you are trying to do correctly, you should have a TransportPlanner Controller, then it will look for your view under Views\TransportPlanner\Index.cshtml.
Please clarify if this is not what you are wanting to do.
I have three types of roles for each of the menu links.
When the Billing guy is logging into the site
how can I determine dynamically the partial.html file that is shown in the content area?
I can not hardcode the content to the first actionlink in the menu, that means that always the Administration is loaded initially.
What can I do in such a case?
These types of decisions are best made in the Controller.
Example:
public HomeController: Controller
{
public ActionResult Administration()
{
// Determine the user's role.
// "GetRole()" does not really exist on the controller - use your own method.
string role = GetRole();
if (role == "Billing Guy")
return View("AdministrationBillingGuy")
else if (role == "SalesGuy")
return View("AdministrationSalesGuy")
else
return View();
// etc.
}
}
I can think of several ways to do this.
if you need all users to get the same url/action then you could do something like this
public ActionResult Custom(RoleEnum userRole)
{
switch(userRole)
{
case RoleEnum.Admin:
.....
return Partial("_adminPartial", viewModel);
// rest of you cases here
}
}
OR:
public ActionResult Custom(RoleEnum userRole)
{
var view = GetViewByRole(userRole);
// where GetViewByRole takes the enum and
// returns a string with the name of the partial
return Partial(view, viewModel);
}
Another way to do this is, and one that I'd recommend is to make an MVC Area for each user requiring a different layout and then at login you can redirect them to the proper Area, I recommend it because it allows for deeper differentiation between roles in the UI layer.
Another way to achieve the different layouts (am talking about MVC Layout Pages similar to ASP.Net Master pages) is to pass a string Layout to the view, using the ViewBag or any other method you like, then in the Razor code you could do something like this:
#model MyViewModel
#{
Layout = (string)ViewBag.Layout;
}
I leaved this last one for last as it appears a bit hacky to me. Hope this helps you
Well, you haven't provided enough information give any explicit direction, but generally, you should just alter your login post action to redirect to a different place depending on some identifying factor like a role (following is pseudocode)
// do login
if (user is "Billing")
{
// redirect to billing action
}
// etc.
The only reason you should be switching out partials or views is if you're doing a SPA (single page application) and utilizing JavaScript for routing. In that case, you would just need some endpoint you could hit with AJAX to get the user's "role".
However, I don't think that's what you're actually doing. If you're just using MVC directly, then you should be actually changing the URL, not just loading a different Razor view.
I have a C# MVC Razor site. Typically, Controllers load views from the Views folder. However, I have a special circumstance where I need to render a view outside of the Views folder. How do I do that?
Controller will load /Views/Random/Index.cshtml
Can't load /Random/Index.cshtml
/Random/test.aspx loads with no issues, but can't change cshtml files to aspx files, they need to be built regularly.
I have tried return Redirect("/Random/Index.cshtml") in the Controller, and currently have no controller at all.
The weird thing is it works on my Production environment, but not in localhost. In localhost I get:
The type of page you have requested is not served because it has been explicitly forbidden. The extension '.cshtml' may be incorrect. Please review the URL below and make sure that it is spelled correctly.
Requested URL: /Random/Index.cshtml
You can definitely do this. For doing this you need to create one new custom view engine like
public class MyViewEngine : RazorViewEngine
{
private static string[] AdditionalViewLocations = new[]{
"~/Random/{0}.cshtml"
};
public MyViewEngine()
{
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(AdditionalViewLocations).ToArray();
base.ViewLocationFormats = base.ViewLocationFormats.Union(AdditionalViewLocations).ToArray();
base.MasterLocationFormats = base.MasterLocationFormats.Union(AdditionalViewLocations).ToArray();
}
}
Then in you global.asax's Application_Start method register this view engine like this-
ViewEngines.Engines.Add(new MyViewEngine ());
If you want your viewengine to take precedence then insert this at 0th position. like this -
ViewEngines.Engines.Insert(0, new MyViewEngine());
return View("~/AnotherFolder/Index.cshtml")` should work for you.
Do not forget to indicate the Layout in your index view:
#{
Layout="~/Views/Shared/Layout.cshtml";
}
In my ASP.NET MVC site, my set up allows users to have roles, and roles have permissions. Generally, these permissions are set for a controller. In my site's main navigational menu, an Authenticated user can see all items, even if they aren't authorized to access that page.
Currently I can only configure the menu based off if the user is authenticated:
#if (Request.IsAuthenticated){ }
I'm wondering, what's the best way to pass the user's permissions to a view, only for the sake of configuring the menu for that user? Is there some common way of doing it, or will I have to implement this myself? I haven't found much information on it, but maybe I'm using the wrong search terms.
Thanks for any advice.
EDIT
Sorry I may not have been clear enough. This is my main nav menu, in the _Layout page. Also, permissions assigned to a role are very configurable by an admin (they can also create and delete roles), so checking if the user is in a role won't meet my needs.
You could create an action in say, the CommonController, which returns a partial view containing your navigation. This partial view can have its own model which can be populated from the controller. This allows you to use dependency injection for instance.
The action could look like this:
[ChildActionOnly]
public ActionResult Navigation()
{
var model = new NavigationModel();
// populate the model..
return PartialView("_Navigation", model);
}
You can render this partial in your view (_Layout.cshtml in your case) like this:
#Html.Action("Navigation", "Common")
For most cases, Request.IsAuthenticated is just fine. Only use this if you need something more advanced.
You can use Roles class static method IsUserInRole:
#if (Roles.IsUserInRole("Admin"))
{
// ...
}
The best way would be to have a property on the viewmodel that the view uses.
I am trying to adapt the AccountController class so that it uses my own backend database. In order to do this completely I need to send a string to the _Layout.cshtml to be checked. How is this possible?
I have tried with ViewData but this requires the Controller to correspond to the view attached e.g. AccountController to View/Account/LogOn.cshtml will work.
I believe that ViewBag works in the same way as I am getting a null reference when I try to access it in the _Layout.cshtml.
At the moment my code is a bit damaged due to trying to fix the problem but here is what I have. Hopefully it will help to explain better.
AccountController/[HTTP-POST] LogOn
...
if (user.GetRole(model.UserName).Equals("Admin"))
{
ViewBag.Role = "Admin";
}
...
_Layout.cshtml
#if (Request.IsAuthenticated && ViewBag.Role.Equals("Admin"))
{
...
}
I no longer think this can be done with ViewBag or ViewData (Due to comments). Any solution would be welcome.
Thank you in advance - Ankou
Change your code to
if (user.GetRole(model.UserName).Equals("Admin"))
{
ViewBag.Role = "Admin";
}
else{
ViewBag.Role = "";
}
You will get an error of ViewBag.Role does not exist. So it must always be set.
Edits
From your comments, I think you might be best creating a Child Action which has it's own controller and does the work for you.
#Html.RenderAction("LogonDisplay");
This will check the Roles, set the ViewBag values, and then display as needed.
You may be using the _Layout before you call Logon action. Therefore, put a check on your _Layout.
#if (Request.IsAuthenticated && ViewBag.Role != null && ViewBag.Role.Equals("Admin"))
{
...
}