We have a requirement to have our ASP.NET MVC websites be automatically closed down by a remote notification (change in database value). Where would be the best place to implement this?
Base Controller Class
Global.asax
Custom attribute
Other
Update
Lots of suggestions to use app_offline but this scenario will be happening daily and will be purely initiated by the database so I would rather have the application take the initiative rather than something external push the file in.
Also, I will probably need to redirect the users to a holding page (preferably an MVC controller method to keep everything consistent). I'm leaning more towards catching it in my BaseController and having that handle it
There's a standard way of "gracefully" terminating ASP.NET 2.0 webapp - just drop a App_Offline.htm to the root directory of your application. See this.
I would go with Global.asax Application_BeginRequest if you have to do it programmatically.
You could Response.Redirect the page to "Offline.aspx" which can retrieve a message from the database or whatever you need. Of course you'd have to look at the request to see if it was trying to get to "Offline.aspx" otherwise you'd end up in an infinite loop.
Or maybe all your applications can be redirected to a single website which would remove most the complication.
I'm going to answer this myself as I did it a different way but thanks to everyone for their responses.
What I ended up doing is overriding OnActionExecuting() in my BaseController class (which all my controllers derived from).
In this method I check the database (using a 1 minute cache) and if the website is closed I load up a view to display a closed message. Code shown below
Protected Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
MyBase.OnActionExecuting(filterContext)
Dim _cfgService = ObjectFactory.GetInstance(Of IConfigService)()
If _cfgService.DynamicConfig.WebSiteClosed Then
filterContext.Result = ErrorHandler(_cfgService.DynamicConfig.WebSiteClosedTitle, _
_cfgService.DynamicConfig.WebSiteClosedMessage)
End If
End Sub
Handling this type of behavior in the Global.asax file sounds like the best solution and redirecting to a static "ofline/closed" page. Handle the request on the Application_BeginRequest method, check to see the the site is active, if it let it continue, if it is not online Response.Redirect the request to the static page.
protected void Application_BeginRequest(object sender, EventArgs e)
{
string redirectURL = "~/Offline.aspx"; //some static page
bool isOnline = false; //SQL Call, config value
if (!isOnline && !string.IsNullOrEmpty(redirectURL))
{
Response.RedirectLocation = redirectURL;
Response.End();
}
}
Sorry, don't know about ASP.NET, but in case helpful:
We have a single APPLICATION.ASP page for our site (CMS / Database merge type stuff); this is possibly not common and therefore may therefore restrict usefulness, but could perhaps be implemented by an INCLUDE at the top of all ASPX files
We rename APPLICATION.ASP to TEST.ASP and copy HOLDING_PAGE.ASP to APPLICATION.ASP
The HOLDING_PAGE.ASP is present in the WWW folder, so always ready-and-available. It contains a "Site not available" message etc. and is self contained for all CSS (no include files, no DB access). Only exception is the company logo (which is external to that file, obviously)
This method prevents all access to the site, is not dependant on having a working DB connection, and allows us to change anything on the site without interfering with the holding page (apart from Company Logo, but changing that is likely to be benign)
We can still access the site, internally, using TEST.ASP - so we can test any new rollout features before removing the holding page and putting the site live. If you want to prevent anonymous use of TEST.ASP then deny anonymous permission.
Remove holding page is: Delete APPLICATION.ASP (i.e. the holding page) and Rename TEST.ASP to APPLICATION.ASP
We also have a database flag that causes the normal APPLICATION.ASP page to show a holding page - which we can use whilst doing more minor changes.
Related
I'd like to simply check from a Controller whether another URL is authorized.
So for example, I'd like to call into a Controller like so:
[HttpPost]
public ActionResult IsUrlAuthorized(string url)
{
bool isAuthorized = // What do I put here?
return Json(isAuthorized);
}
So I'd like to know what I could call to check on whether the current user is authorized for the passed-in URL or not. I'm guessing the answer has something to do with Routes, which sit a little bit outside MVC?
This is a somewhat similar question but not quite the same thing:
ASP.NET MVC. Check if user is authorized from JavaScript
Since the user may or may not be authorized in general, but may not have the right permissions or role assignments to see a specific URL.
Ideas?
Update: I use standard MVC authorization attributes to lock down my app, so I'll just give an example of what that looks like here. In MVC Routes map to Controllers. A single method on a Controller can be restricted to one or more Roles:
public class HomeController : Controller
{
[Authorize(Roles = "User, Moderator")]
public ActionResult ListRecentPosts()
{
. . .
}
}
Or, an entire Controller can be restricted to one or more roles:
[Authorize(Roles = "Admin")]
public class AdminController : Controller
. . .
The actual URL that any of these controller methods responds to is based on a default mapping in a standard MVC app:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
But, you can be nice to your users and make URLs guessable by adding a lot more Routes - as a result, a Controller method can have many names that point to it. You can't just assume and infer the controller name from the URL (even if it maps out that way for half the URLs in the site).
So presumably I either need a way to ask the Routing engine directly whether a URL is authorized for the current user, or a 2-step of asking the Routing engine for which Controller and Method, then ask if those are authorized - hopefully not by using Reflection and matching Roles directly as that again would appear to assume too much.
Update 2: The way this came up is I have an Account strip at the top of my app. Its state can change by selecting one of several accounts you're authorized as. Depending on where you are in the app, the account you chose might have authorization to view this page - and you might be in the middle of filling out a form you don't want to lose. So the naive approach - just refresh when they pick another account - is harmful, and a waste of the user's time even if there is no form and they're just reading a page that's all text.
While that convenience to the user is nice, the user is going to fairly assume that pages they can't see as a user who shouldn't have permission really are denied (and, it would be harmful to leave them on a page that's forbidden - actions taken from it will fail). So I need to know whether to redirect away based on their new permissions.
One of the things I love about .Net is the way many of its best libraries decompose so well, so you can easily recompose things that are part of its normal functionality, or a new twist. Both the Routing module and MVC appear to be very well constructed, so I have to suspect this can be done.
The cheap hack is to ensure that my authorization module returns a consistent redirect status code when a user isn't authorized, and when the user changes their account in the account strip, fire 2 AJAX calls: One to change account, and then a second to the current page over AJAX just to check the HTTP Status Code. 200 OK means leave the page as is, Redirect means follow the redirect. Obviously this is a little ugly, involves an extra HTTP call, creates a false hit in the logs, and makes an assumption about how authorization is handled across the app.
There could be a secondary concern - the page might be authorized, but just change how it works or looks. This particular app has no change in look based on account (besides the account strip itself), and I can handle functionality changes by just providing a custom event that forms listen to - they can reload any relevant data from the server in response to it.
Using UrlAuthorization.CheckUrlAccessForPrincipal only works if you're only using URL authorization. But for MVC using Routing, we highly recommend that you don't use URL authorization to secure an app.
Instead, we recommend using Authorization attributes on the controller class. The reason is there could be multiple URLs that call the same controller action. It's always better to secure the resource at the the resource and not just at the entry ways.
In this particular case, you'd have to get an instance of the controller given the URL. THat's a little tricky as you'll basically have to run the MVC pipeline from the point where you have the URL to the point where you have the controller. It's possible, but seems heavyweight.
I wonder if there isn't a better and simpler way to accomplish your goals. What is it you're really trying to do?
UPDATE: Based on your scenario, it sounds like this is an initial check just for UI purposes. Perhaps all you need to do is make an asynchronous Ajax request to the URL and check the HTTP Status code. If it's a 401 status code, you know the user is not authorized. That seems like the safest bet.
How about UrlAuthorizationModule.CheckUrlAccessForPrincipal method.
UrlAuthorizationModule.CheckUrlAccessForPrincipal Method (System.Web.Security)
We are building an ASP.NET MVC application which will be deployed behind a hardware load balancer that supports, among other things, caching.
Our proposal is to manually define which URL patterns should be cached by the load balancer. This will be quite an easy process for us as we have 'catalogue' pages which are relatively static, then 'order' pages which are not.
Must avoid using session state on cached pages, as the entire response is cached by the load balancer - this includes any cookies that are sent.
Ideally there would be an attribute which can be applied to controllers or action methods which allows selective use of session state, but there doesn't appear to be one. I realise that an approach like this would result in lost sessions if the use leaves the 'session zone' - that's fine.
Other than re-implementing the entire ASP.NET MVC HTTP controller... any suggestions?
Thanks in advance.
This is now moved from Futures into MVC3. There's a ControllerSessionState attribute (apparently will be named SessionState for the final release of MVC3), which can be applied to a controller, something like this:
[SessionState(SessionStateBehavior.Disabled)]
public class MyController : Controller
{
...
(But in the RC version, you must use ControllerSessionState
This is included in MVC 2 Futures. See http://blogs.msdn.com/rickandy/archive/2009/12/17/session-less-mvc-controller.aspx for more info.
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.
Let's say I have a Web application implemented like a set of wizard pages to edit a complex object. Until the user clicks on the "Finish" button, the object doesn't get saved to the back-end system (a requirement), so in the meantime I have to keep the whole information about the object in some kind of a session state.
Also, some of the wizard pages have to show combo and list boxes with potentially large number of items. These items are fetched from the back-end system using a Web service.
Coincidentally, the wizard allows the user to freely jump from one wizard page to any other (using tab links on top of the form), so it's not a simple "next, next... finish" thing.
Additional constraint: the Web application runs on a Web farm and the customer is weary of using server-side session state. In the best case they want to keep the size of the session state minimal (they had problems with this in the past).
So basically there are two problems here:
How/where to keep data entered by the user in the Wizard?
Whether to cache the combo/list items received from the back-end and if so, where?
Options I'm considering:
Storing the object in a WebForms-like ViewState (by serializing it into the HTML page). This would also include the combo box items. Obviously, there could be a problem with HTML pages becoming very large and thus Web application will be slow.
Storing it into server-side session state, regardless of the customer's wishes and without knowing how the performance will be affected until it is tested on the actual Web farm (late in the project).
I cannot decide between the two. Or is there another alternative?
Why cache at all? You could just have the tabbed pages where each page is a div or panel and just display the current div relating to your tab. That way you dont have to keep track and process all the inputs when the user submits the form.
Is it possible to store the wizard data in a temporary table in the database? When the user finishes the wizard the data is copied from the temporary table and deleted. The temporary table includes a timestamp to remove any old uncompleted data.
As Daisy said, it doesn't have to be cached. You could also use hidden form fields. Because these could map to the same object on each controller action, you could progressively build the object through successive pages.
//Here's a class we're going to use
public class Person
{
public int Age {get;set;}
public string Name {get;set;}
public Person()
{
}
}
//Here's the controller
public Controller PersonCreator
{
public ActionResult CreatePerson()
{
//Posting from this page will go to SetPersonAge, as the name will be set in here.
return View();
}
public ActionResult SetPersonAge(Person person)
{
//This should now have the name and age of the person
return View(person);
}
}
//Here is your SetPersonAge, which contains the name in the model already:
<%= Html.Hidden("Name", Model.Name) %>
<%Html.TextBox("Age") %>
And that's pretty much it.
I can suggest a few more options
Having the entire wizard as a single page with the tabs showing and hiding content via javascript on the client-side. This may cause the the initial page to load slower though.
Caching the data at the server using the caching application block (or something similar). This will allow all the users to share a single instance of this data instead of duplicating across all sessions. Now that the data is lighter, you may be able to convince the customer to permit storing in the session.
There is a lot of resistance in the MVC community against using Sessions. Problems are that a lot of us developers are building login systems like a bank website. One could argue for hidden fields and that works for some situations but when we need to time a user out for security and compliance, then you have several options. Cookies are not reliable. Relying on Javascript timers are not reliable and are not 508 compliant as the goal should be to degrade gracefully. Thus for a Login, a Session is a good option. If you write the time to the client browser, to the server database or server file system, you still have to manage the time per user.
Thus use Sessions sparingly, but don't fear them. For the wizards, you technically can serialize hidden fields passing them around. I suspect the need and scope will become much greater and an authorization/authentication implementation with Sessions will be the crux of the application.
If you cannot use ajax (for validation & dropdowns and ability to convert wizard to tabbed page) and cannot use html5 (for dropdown caching and form state saving in local storage), then I think you are pretty out of available "best practices" and you have to resort to bad (or worse) one.
As MVC is opponent of WebForms regarding session usage, maybe you can use a workaround? For example, besides storing all these values in some temporary database records you need to clean up later, you could set up AppFabric extension for Windows Server and use it to store dropdown list items (and scope can be for all users, so if more users are using system at the same time you need only one call to web service to refresh cache), and also to temporary store your objects between steps. You can set your temporary objects in AppFabric to automatically expire so cleanup is not necessary. It can also be of help for speeding up other parts of your system if you extensively call another system over web services.
I've been dealing with the same issue and, while my requirements are a little simpler (keeping state for just a few strings), my solution may work for you. I'd also be interested in hearing others thoughts on this approach.
What I ended up doing is: in the controller I just dump the data I want into the Session property of the Controller and then pull it out next time I need it. Something like this for your situation:
//Here's the controller
public Controller PersonCreator
{
public ActionResult CreatePerson()
{
//get the age out of the session
int age = (int)(Session["age"]);
//do something with it...
return View();
}
public ActionResult SetPersonAge(Person person)
{
//put the age in the session
Session.Add("age", person.Age);
return View(person);
}
}
The thing I like about this is I don't have to put a bunch of hidden params around on my view pages.
The answer to this can be found in Steve Sanderson's ASP.NET MVC 2/3, and requires a reference to the MVC Futures assembly. This link to Google Books is exactly what he does.
In essence, you serialize the wizard data to the View. A hidden field is rendered storing all of the acquired information.
Your controller can work out what to do through the use of the OnActionExecuting and OnResultExecuted (to cater for redirects) to pass it to the next view.
Have a read - he explains it much more thoroughly than I can.
I'm experimenting with MVC, and my question is - where I had Page_Load logic in Master Pages with WebForms, where should it go in MVC? Here's the business case:
Different Host Headers should cause different Page Titles to be displayed on the site's (one) Master Page, therefore all pages. For example, if the host header is hello.mydomain.com, the page title should be "Hello World" for all pages/views, while goodbye.mydomain.com should be "Goodbye World" for all pages/views.
If the host header is different than anything I have in the list, regardless of where in the application, it should redirect to /Error/NoHostHeader.
Previously, I'd put this in the MasterPage Load() event, and it looks like in MVC, I could do this either in every controller (doesn't feel right to have to call this functionality in every controller), or somewhere in Global.asax (seems too... global?).
Edit: I have gotten this to work successfully using the Global.asax method combined with a Controller for actually processing the data. Only problem at this point is, all of the host header information is in a database. I would normally store the "tenant" information if you will in a Session variable and only do a DB call when it's not there; is there a better way to do this?
There is no 1:1 equivalent in MVC for a reason, let's just recapitulate how to think about it the MVC way:
Model: "Pages of this site are always requested in a certain context, let's call it the tenant (or user, topic or whatever your sub domains represent). The domain model has a property representing the tenant of the current request."
View: "Render the page title depending on the tenant set in the model."
Controller: "Set the tenant in the model depending on the host header".
Keep in mind that what we want to avoid is mixing controller, view and business logic. Having controller logic in more then one place or a place, that is not called "controller" is not a problem, as long as it remains separated.
And now the good thing: You could do this "MVC style" even with Web Forms, and the solution still works with ASP.NET MVC!
You still have the request lifecycle (not the page lifecycle), so you could implement a custom HttpModule that contains this part of the controller logic for all requests. It handles the BeginRequest event, checks for the host header, and stores the tenant to something like HttpContext.Current.Items["tenant"]. (Of course you could have a static, typed wrapper for this dictionary entry.)
Then all your model objects (or a model base class, or whatever is appropriate for your solution) can access the HttpContext to provide access to this information like this:
public string Tenant
{
get { return HttpContext.Current.Items["tenant"]; }
}
Advantages:
You have separated cause (host header) and effect (rendering page title), improving maintainability and testability
Therefore you could easily add additional behavior to your domain model based on this state, like loading content from the database depending on the current tenant.
You could easily make more parts of the view depend on the tenant, like CSS file you include, a logo image etc.
You could later change the controller logic to set the tenant in the model not only based on the sub domain, but maybe based on a cookie, a referrer, search term, user agent's language, or whatever you can think about, without modifying any of your code depending on the model.
Update re your edit: I don't like the idea of holding the state in the session, especially if your session cookie might apply not only to each sub domain, but to all domains. In this case you might serve inconsistent content if users visted another sub domain before. Probably the information in the database that is mapping host headers to tenants won't change very often, so you can cache it and don't need a database lookup for every request.
You could create a base controller that supplied the correct ViewData to your MVC Master Page View, then derive each of your actual controllers from that one. If you put the logic into the ActionExecuting method, you should be able to generate an exception or redirect to an error page if necessary.
You are thinking too "WebForms" and not enough MVC. A master page is just a wrapper of your view, and it should only contain layout html. You can send stuff to your master, but it's a one way road and you should strive for agnostic views. Bottom line: forget about the events that WebForms had as they aren't going to be used here.
Since you are dealing with Host headers I suppose you could put it in the Global.asax...great now I'm confused :P
Stolen code from http://forums.asp.net/t/1226272.aspx
protected void Application_BeginRequest(object sender, EventArgs e)
{
string host = string.Empty;
if (this.Request.ServerVariables["HTTP_HOST"] == this.Request.Url.DnsSafeHost)
{
host = this.Request.Url.DnsSafeHost;
}
else
{
Regex regex = new Regex("http://[^/]*.host/([^/]*)(/.*)");
Match match = regex.Match(this.Request.Url.AbsoluteUri);
if (match.Success)
{
host = match.Groups[1].Value;
Context.RewritePath(match.Groups[2].Value);
}
}
// Match the host with the portal in the database
...
}
What you need is here http://www.asp.net/mvc/tutorials/passing-data-to-view-master-pages-cs