Using File.Exists() to check if views exist on disk - asp.net-mvc

I'd like to build a way to override for ASP.NET MVC views at runtime. Basic idea is to have MyView.cshtml and and optional MyView.Override.cshtml. As soon as MyView.Override.cshtml is present on the disk, it shall be used instead of the original view.
What I'm trying to do is:
protected new ActionResult View(string viewName)
{
var overridePath = viewName.Replace(".cshtml", ".Override.cshtml"); // i.e. ~/Views/MyView.Override.cshtml
if (System.IO.File.Exists(Server.MapPath(overridePath)))
{
return base.View(overridePath); // use override
}
return base.View(viewName); // use default
}
This basically works. My question: Is it good practice to do use File.Exists() when resolving views? Will this introduce any issues (Flexibility, Performance)? Am I missing a standard MVC way to do the same thing?

If you want this possibility for EVERY page in your application, then there is already a way to do this called DisplayModeProvider. The typical use-case for this is for having mobile or browser-specific Views.
For example, you can set up an Index.Mobile.cshtml, and the built-in provider will return that view instead of Index.cshtml if it determines that the requesting browser is a mobile device based on the User Agent string.
However, you can create your own provider to add additional ones that will return for whatever reason you wish.
Inside your Startup or Global.asax (depending on OWIN, etc), you will want to add the following code:
DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("Override")
{
// Make it always try this
ContextCondition = (ctx => true)
});
With this in your code, it will always try to return a xxxx.Override.cshtml view. If that doesn't exist, it will fall back to the regular view.

Related

Enforcing a choice prior to viewing MVC and Web Forms pages

I'm working on a system that needs to know a user's choice before they enter a site. Up till now the choice has been stored in a cookie and checked by JavaScript on the page load - if the cookie doesn't exist then a dialog is shown and the user makes the choice.
Although we'd normally expect the user to arrive at the homepage of the application, they can legally follow a URL to any page within the application, so the JavaScript to check the choice exists on every page.
This has caused problems (almost always fixed by clearing cookies) so we're switching to store the choice in the database. What we need is a neat way of making sure that all pages (MVC and Web Forms) check that the choice has been made and, if it hasn't, either display a dialog or redirect to a page where the choice can be made.
The main thing bothering me is that to cause a redirect using MVC, I need to return a RedirectResult, and this can only be done from an Action. I don't want every action to have code regarding this check - it seems like the kind of thing that should be possible from a base controller (in the same way a base page could cause a Response.Redirect.
Can anyone suggest a good way for all pages to perform a check on the database and then either cause a redirect or show a dialog?
The main thing bothering me is that to cause a redirect using MVC, I
need to return a RedirectResult, and this can only be done from an
Action.
Oh not at all. You could also redirect from a custom action filters.
For example you could write a custom IAuthorizationFilter that will check whether the user made the necessary choice and if not redirect to some given page. The check could be done against a cookie, database or wherever you decide to persist this information:
public class EnsureChoiceHasBeenMadeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// get the current user
var user = filterContext.HttpContext.User;
if (user.Identity.IsAuthenticated && !UserMadeAChoice(user.Identity.Name))
{
// if the current user is authenticated and he didn't made a choice
// redirect him to some page without even attempting to execute
// the controller action that he requested
var values = new RouteValueDictionary(new
{
controller = "home",
action = "index"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
private bool UserMadeAChoice(string username)
{
throw new NotImplementedException();
}
}
Now you have different possibilities:
You decorate the controllers/actions that you want to perform this check with the [EnsureChoiceHasBeenMade] attribute
You register the action filter as a global action filter so that it applies to absolutely all actions
You write a custom filter provider in order to dynamically apply the action filter to some actions based on some dynamic values (you have access to the HttpContext).

What's the recommended pattern for handling control display logic in ASP.NET MVC?

I'm going through some of the MVC3 tutorials, namely the Pluralsight videos, and I'm thinking of how our existing application would function if it were overhauled to ASP.NET MVC (not the plan, but it gives me a frame of reference). We have quite a bit of code that looks something like this in our aspx code-behinds:
if (SomeBooleanCheck){SomeControl.Visible = true;}else {SomeControl.Visible = false;}
Granted that example is greatly simplified, but assuming the boolean logic is fairly complex and assuming multiple things need to happen as part of making the control visible (maybe changing color, size, text, etc.) what's the pattern for doing this in ASP.NET MVC? It seems like you'd have to do that same boolean checking in the view itself, which to me seems kind of ugly. Seems like there has to be a better way and this surely came up on MS's use case list, I'm just not seeing the answer.
The approach you may take will vary greatly depending on the specific scenario. A few options incude:
Doing as you say and adding the conditional in the view
Abstracting the conditional (if it is complex) into your view model so that the lines in the view are still simple (just accessing a preset boolean value on your view model).
Doing this conditional at the route or controller level and calling a different overall view (which may share a layout (razor) or master view (webforms mvc))
You don't mention explicitly how you would render the controls in the conditional. I assume you would be doing a RenderPartial. So the lines themselves in the view would be quite 'small'.
if(myViewModel.ComplexBoolean) // Boolean set in generation of view model
Html.RenderPartial('firstPartial')
else
Html.RenderPartial('secondPartial')
EDIT: If the item you are setting as 'visible' is simply a single control you may just output the control directly e.g.
if(myViewModel.ComplexBoolean) {
Html.DropDownListFor(m => m.Type, Model.Types /* An IEnumerable<SelectListItem>*/, new { #class = "myList" });
}
Additionally if you didn't want to set that 'Model.Types' property (to save a db hit for example) then the conditional could be in the location you create your view model (either the controller or some service/view model repo). The view could then just check for the properties existance instead:
if(Model.Types != null) {
Html.DropDownListFor(m => m.Type, Model.Types /* An IEnumerable<SelectListItem>*/, new { #class = "myList" });
}
If your controls does not use the data found in your View's ViewModel, you can also use Html.RenderAction to call Child Actions. For example, suppose you want to display a different menu to users with different roles. You can call #{Html.RenderAction("Menu", "Account");} in your View, which will call the "Menu" Action in your "Account" controller. Your complex Boolean logic and the logic to formulate your controllers’ settings will reside in the "Account" controller's "Menu" action. The "Menu" action will decide what Partial View/Controller to display.
// This goes in your View (clean single line!)
#{Html.RenderAction("Menu", "Account");}
// This goes in your controller
[ChildActionOnly]
public ActionResult Menu()
{
bool isAdmin = false;
// Your complex boolean logic goes here
// Set your controller settings here
string controllerSettings = ""; // Use class or array for multiple settings
if (isAdmin)
{
return PartialView("~/Views/Account/_AdminMenu.cshtml", controllerSettings);
}
else
{
return PartialView("~/Views/Account/_StandardMenu.cshtml", controllerSettings);
}
}

asp.net mvc how it decides which view to load

I am attempting to construct an asp.net mvc app which will use the urls like:
/Controller/[Number]/Action/Id
I have got it to always call my controller and pass it the Number and the Id fine...
However I now want to return a different view depending on the Number
I could have options like:
if([Number] == 1) { return View("ViewName");}
if([Number] == 2) { return View("ViewName2");}
however I instead was wondering if there was a way to change the core so that instead of searching at ~/Views/controller/action.aspx I could have my own method which did some checking on the Number then passed to the virtual file provider is a different path
Hope this makes sense!
Decide which view to load, depending on input parameters is a controller task. You could write your own view engine.
But it is easier to return the full path to the view you want to return.
return View("~/myviews/ViewName3.aspx");
This will render ViewName3 from given directory.
You might want to look at decorating your controller method with Action Filter Attributes.
Then, you could do something special inside the Action Filter Attribute.
Or, you could pass Number to a Model object, then have the model Object return the right View path.
Either way, your instinct of trying to keep too much logic out of the Controller is sound, especially if [Number] is somehow a business concern and not a view concern.
You need to look into / google creating a custom view engine.
By the sounds of things you probably just want to extend the built-in WebFormViewEngine and just override the locations and the .FindView() method.
HTHs,
Charles

ASP.net MVC - request-scoped global variable

I have a value which I want to be vaild during a single request. I am not using Session, as this would make the value global for the entire navigation session.
So I have put thie value in a static field of a class. Great, but then I discovered that such fields are even more global, that is, they stay set for the entire application! This means that there could be random interaction among navigation sessions.
So the question is: is there a safe place I can put a global variable, which will be
global throughout the request
reset after the request is completed
not affected by any other request, either of the same user or by other users
Thanks
Palantir
EDIT
I'll elaborate. I have a piece of code in my master page, which I need to hide on certain conditions, of which I am aware in the controller only. I thought about setting a static variable in the controller, which then would be queried by the master page, but now I see there could be a better way...
Use HttpContext.Items - a per-request cache store. Check out this article on 4guysfromrolla for more details.
It should work fine in ASP.NET MVC. You may wish to derive your master page from a base class (either via code-behind or using the Inherits directive) and have a protected method on the base class that inspects HttpContext.Items and returns, e.g. true/false depending whether you want to display the conditional code.
TempData lasts until the next request as already noted.
But there are also two other dictionaries scoped to the single request.
{Controller,ViewPage}.ViewData
Context.Items
To communicate from controller to (master) page ViewData is probably the better choice.
Two approaches come to mind:
Create a base controller where you set this variable, and then have all your controllers inherit from that.
Use TempData - the problem here being that it sticks around for the next request. But maybe knowing that, you can work around it by using a GUID key to determine that you are, in fact, getting a new value when you need it.
I would probably go with 1).
The common way to access data in a MasterPage that is set in Controller (Action) is via ViewData["TheDataKey"] = "SomeValue".
This is relatively easy and there are a couple of ways that you can do it - depending on how your site works.
I'm interpreting your request as that you want a property or variable that exists for the duration of the request and is visible to the controller, model and master.
A static property is visible to the current application in ASP this means a load of users connecting at once, but not necessarily all of them. IIS will spawn new ASP applications as it needs to.
So the ways you can do this:
You can have a custom base class for your master page or a code-behind page (as all the WebForms stuff still works)
You can have a custom base class for your controllers.
You can get to one from the other, so:
void Page_Init( object sender, EventArgs e )
{
var ctrl = this.ViewContext.Controller as MyBaseController;
if ( ctrl != null )
{
MyLocalProp = ctrl.PropOnMyController;
}
}
This will then be available in the controller and the master page on a per Request basis.
Did you look into the tempData that is attached to the controller class? it is a simple dictionary that preserves it's value through one single request.That would meant that your data can only be accessed in the controller but that should not be a problem.
public class MyController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult MyAction(string id)
{
this.TempData["Message"] = "YourData";
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyAction(string Id)
{
var myData = this.TempData["Message"];
}
}
This works for me. I use it only to display warning messages and stuff like that.

Pass data to the view. Always through viewData ?

I’m building an application that monitors other systems. Now I want to implement partial view, a User Control called “status”. This control shall display status information about the application.Like:
user logged in,
How many systems online,
Latest activity.
This partial view shall be rendered in nearly all other views. How shall I pass this information to the view?
I don’t want to write
Wiewdata[“SystemsOnline”] = Helpers.CountSystemsOnline()
Wiewdata[“SystemLatestActivity”] = ………………
in all my actions.
Can I write something like Html.RenderPartial(../Shared/Status) that fist go to an action that adds the viewdata?
Or shall i access the information directly in the view trough the hepler?
I noticed that the defult LogOnUserControl view use Page.User.Identity.Name to directly access that data.
When is it ok to not pass data throug viewdata in the controller?
You have to use the ViewData class for that purpose. I do not recommend to invent tricks instead.
But you do not need to manually pass the same data from every action. Just do it in your base controller:
protected override void OnActionExecuted(ActionExecutedContext context) {
base.OnActionExecuted(context);
if (context.Result is ViewResult) {
// Pass the systems online to all views
ViewData[“SystemsOnline”] = Helpers.CountSystemsOnline();
}
}
Only a couple of lines of code and you make it available everywhere.

Resources