I am using the Redirect After Post pattern in my ASP.NET MVC application. I have
the following scenario:
User goes to /controller/index where he is asked to fill a form.
Form values are POSTed to /controller/calculate.
The Calculate action performs calculation based on input and instantiates a complex object containing the results of the operation. This object is stored in TempData and user is redirected to /controller/result.
/controller/result retrieves the result from TempData and renders them to the user.
The problem with this approach is that if the user hits F5 while viewing the results in /controller/result the page can no longer be rendered as TempData has been expired and the result object is no longer available.
This behavior is not desired by the users. One possible solution would be instead of redirecting after the POST, just rendering the results view. Now if the user hits F5 he gets a browser dialog asking if he wants to repost the form. This also was not desired.
One possible solution I thought of was to serialize the result object and passing it in the URL before redirecting but AFAIK there are some limitations in the length of a GET request and if the object gets pretty big I might hit this limitation (especially if base64 encoded).
Another possibility would be to use the Session object instead of TempData to persist the results. But before implementing this solution I would like to know if there's a better way of doing it.
UPDATE:
Further investigating the issue I found out that if I re-put the result object in TempData inside the /controller/result action it actually works:
public ActionResult Result()
{
var result = TempData["result"];
TempData["result"] = result;
return View(result);
}
But this feels kind of dirty. Could there be any side effects with this approach (such as switching to out-of-process session providers as currently I use InProc)?
Store it in the Session with some unique key and pass the key as part of the url. Then as long as the session is alive they can use the back/forward button to their heart's content and still have the URL respond properly. Alternatively, you could use the ASP cache, but I'd normally reserve that for objects that are shared among users. Of course, if you used the parameters to the calculation as the key and you found the result in the cache, you could simply re-use it.
I think redirect after post makes much more sense when the resulting Url is meaningfull.
In your case it would mean that all data required for the calculation is in the Url of /controller/result.
/controller/calculate would not do the calculation but /controller/result.
If you can get this done thinks get pretty easy: You hash the values required for the calculation and use it as the key for the cache. If the user refreshes he only hits the cache.
If you cant have a meaningfull url you could post to /controller/index. If the user hits F5 calculation would start again, but a cache with the hash as key would help again.
TempData is generally considered useful for passing messages back to the user not for storing working entities (a user refresh will nuke the contents of TempData).
I don't know of more appropriate place than the session to store this kind of information. I think the general idea is keep session as small as possible though. Personally I usually write some wrappers to add and remove specific objects to session. Cleaning them up manually where possible.
Alternatively you can store in a database in which you purge stale items on a regular basis.
I might adopt a similar idea to a lot of banks on their online banking sites by using one-time keys to verify all POSTs. You can integrate it into a html helper for forms and into your service layer (for example) for verification.
Let's say that you only want to post any instance of a form once. Add a guid to the form. If the form does not post back and the data is committed then you want to invalidate the guid and redirect to the GET action. If say the form was not valid, when the page posts back you need a new (valid) guid there in the form waiting for the next post attempt.
GUIDs are generated as required and added to a table in your DB. As they are invalidated (by POSTS, whether successful or not) they are flagged in the table. You may want to trim the table at 100 rows.. or 1000, depending on how heavy your app will be and how many rendered but not yet posted forms you may have at any one time.
I haven't really fine tuned this design but i think it might work. It wont be as smelly as the TempData and you can still adhere to the PRG pattern.
Remember, with PRG you dont want to send the new data to the GET action in a temp variable of some sort. You want to query it back from the data store, where it's now committed to.
As Michael stated, TempData has a single purpose -> store an object for one trip only and only one trip. As I understand it, TempData is essentially using the same Session object as you might use but it will automatically remove the object from the session on the next trip.
Stick with Session imho rather than push back in to TempData.
Related
I have one method which receive two parameters (int?, string). Action in my controller must save this variables and return view with form. After form is returned from client I need this variables from previous request. I can pass them to view with ViewBag and add to form as hidden, but it is very dangerous, anyone can change it in browser. Any ideas?
I think this question does not make sense.
If I have method which receives two parameters with HTTP GET method, so it's no difference where user change this two variables, in get method or in form.
If I store it in server side, he can change when passing parameters to first method and server will store wrong variables.For example:http://www.someurl.com/Controller/Action/?id=123&key=someKeyI can change parameters by editing url, so absurdly store it as variable at server side to make sure that user wouldn't change it by editig hidden fields. Moreover I can check if key is the same key as in database with this id.
Session time is 20 or 30 minutes. User can open this form and submit it when the time of session is out. Users don't like exceptions
It's pretty standard to allow forms to expire after a certain amount of time. You can keep sessions open forever as that's a security hole.
As for the actual value, you can remove it from the session as soon as the second page has been processed. If the value is not there when the second page is hit, simply redirect to the first one again and show a message that says that it took too long time.
Your second option is to use TempData with is specifically designed for storing values between two pages. TempData cannot be accessed by the user.
The third option is of course to store the value in a database (to keep it as long as you want, between logins etc)
You can store any object you like in the current session of the user. In your controller you can use:
// Save value to session
Session["MyVariableName"] = 32;
// Read value from session
int myVariable = (int)Session["MyVariableName"];
For more info regarding the session see: https://msdn.microsoft.com/de-de/library/system.web.httpcontext.session(v=vs.110).aspx
I have a .net application with a Form layer, a DB model layer (entity framework) and a Controller layer between this two layers.
I need to handle this situation:
User presses a button to edit some params
The form needs to request some DB data that represents the current state of those params
Possibly, the user request could be rejected because is N/A to current situation, in this case an error message box should be shown
A modal form is shown, the user changes params and confirm
Changes are made in the DB model
That's pretty simple.
The fact is that, at point 4, we need some of the data we already processed at point 2.
In particular:
at point 2 we request some data to the DB model, that data is likely not to be in cache, so a SQL query is performed
that data is processed by a local LINQ
state of several checkboxes to show in the modal form is returned
at point 4 we need again LINQ processed data
since we came from the Form layer, we do not have that data anymore
therefore data is requested again to the DB model, but this time it's in cache
that data is processed again by local LINQ
Is it worth to re-load and re-process data to maintain the MVC pattern?
I don't know how it works exactly in VB.NET, but if we look at this problem in a pure "MVC" way (at least, how I understand it), something is not right.
In this step, when the click is done, the form call the controller (all action pass by the controller)
The controller then needs to do the validation. If it needs the database to do that, so be it. Then, it redirect the user to a view. (Should it be a message box or another form to enter data)
Here, the user do the change in the form and then click on a button to submit. In this button, you call the controller again (another function/action).
In the controller, you can do the needed validation and insert/update the data in the database via LINQ. Then, you can redirect to the view.
Since a lot of time could have passed between the step 2 and step 4 and that the data could have changed between the 2 calls, I think that doing the request 2 times is ok. Also, since they are 2 different function in the controller, I don't think you have the choice.
That's how I see it, but I can be wrong :)
EDIT
I didn't know that the query to the database were time consuming and that it was an issue.If the absolute goal is to NOT make the user wait twice since time is important in this application, I guess you could store the object that you get at step 2 in memory and retrieve it with the controller (with some kind of helper class). It's like doing the query in the database, but in the memory. If you use the repository pattern, then the programmer who's coding the logic in the controller will not even know that he's querying something else than the database since it's another level of abstraction. You could free the memory right after the step 4.
Oh I'm not 100% sure but the flow pattern in your question does not look right?
The usual procedure is to DISPLAY the DATA and have an edit button there with the dataview
So you may have something like
Function ShowAddressDetails(OwnerId as long) as ActionResult
And your ActionResult is usually a MODEL that is to be passed to the VIEW
maybe (keeping with the address record sample) something like...
Return View(AddressRecordModel)
where the address record is extracted from SQL DB using the OwnerId parameter
And in your VIEW where your EDIT button is,
You have at least two choices,
Those being
1. Reload data from SQL (used where data may have changed since last action)
2. Pass the already loaded Model (Used where the data hasnt changed)
which would mean tha you have either (or both) of the following
Function EditAddressDetails(OwnerId as long) as ActionResult
or
Function EditAddressDetails(Model as AddressRecordModel) as ActionResult
Alternatively you may have "CHILDACTION"s as opposed to "ACTION"'s
Also do not forget the following...
in a HTTP GET request, the model is passed from the CONTROLLER TO the VIEW
in a HTTP POST request the Model is passed from the VIEW to the CONTROLLER
So you should indeed have the model (data)?
Finally if the sequence is ONLY used by ONE user then the data should not have changed between requests UNLESS and EDIT/AMEND/UPDATE request was completed successfully.
I have an application that shows many charts and tables using JQuery. Some of these charts are based on variables that are saved in the session (E.g. user added a value in another page and in the next page I am generating a chart, so the user request doesn't send any parameters)
I was looking around on the net and most of the solutions are based on
[OutputCache(Duration=60, VaryByParam="someParm")]
The problem is most of my request don't send parameters, they just use values that are in the session.
Is there any way to enable cache for these kinds of requests?
Edit: We have a complex security requirement that we couldn't use the default authorization attribute of MVC. We had to write logics based on the current user + the parameters sent to the action, so a method inside the action decides either to go ahead with the request or returns nothing. This makes caching very difficult because at the time OutputCache is executed we just have parameters, but identity object in the context is empty. As a result, if a user with admin privilege send a request for a and b and after him someone with minimum privilege send request for a and b, the second person will see the result because the action didn't run, but the value from the cache is used!
To solve this problem I used the getvarybyCustome. All this function does is to return user's group name which helps to create a more complex key. The person with minimum privilege in the last example will have different cache key (a,b,group_less) than the admin's request cache key (a,b,group_admin). However, getting's group name for each request is expensive as well, so I use Cache object to cache user's group, so at the beginning of the session the user's group is queried from AD and saved to cache, so for his/her later requests, his group name is retrieved from cache.
If something you can't achieve by VaryByParam then you can try VaryByCustom. See an example here
You could make a redirect of this request and send it to a new controller method sending the session parameters, by this way in a future implementation may be you use query string parameters instead of session and your code will work too.
You could make a method for conversion of this session parameters on a base class of all your controllers, to write the conversion once.
Here's a simple problem: users want to edit products in grid-like manner: select and click add, select and click add... and they see updated products list... then click "Finish" and order should be saved.
However, each "Add" have to go to server, because it involves server-side validation. Moreover, the validation is inside domain entity (say, Order) - that is, for validation to happen I need to call order.Add(product) and then order decides if it can add the product.
The problem is, if I add products to order, it persists changes so even if users do not click "Finish" the changes will still be there!
OK, I probably shouldn't modify the order until users click Finish. However, how do I validate the product then? This should be done by the order entity - if product is already added, if product does not conflict with other products, etc.
Another problem, is that I have to add product to order and "rebuild view/HTML" based on its new state (as it can greatly change). But if I don't persist order changes, the next Add will start from the same order each time, not from the updated one. That is, I need to track changes to the order somehow.
I see several solutions:
Each time the user click Add, retrieve order from database, and add all new products (from the page), but do not persist it, just return View(order). The problem is I cannot redirect from POST /Edit to GET /Edit - because all the data only exists in the POST data, and GET lose it. This means that Refresh page doesn't work in a nice way (F5 and you get duplicated request, not to mention the browser's dialog box)).
Hm, I thought I can do redirect to GET using TempData (and MvcContrib helper). So after POST to /Edit I process business logic, gets new data for view, and do RedirectToAction<>(data) from MvcContrib that passes data via TempData. But since TempDate is... temp... after F5 all the data is lost. Doesn't work. The damn data should be stored somewhere, this way or another.
Store "edit object" in Session with the POST data (order, new products info). This can also be database. Kind of "current item - per page type". So page will get order ID and currently added products from this storage. But editing from multiple pages is problematic. And I don't like storing temp/current objects in Session.
Marking products as "confirmed" - if we do /order/show, we first cleanup all non-confirmed products from the order. Ugly and messy logic.
Make a copy of the order - a temporary one - and make /Edit work with it. Confirm will move changes from temp order to persisted. A lot of ugly work.
Maybe some AJAX magic? I.e. "Add" button won't reload page but will just send new + already added products to server, server will validate as order.Add(products + newproduct) but will not persist changes, will just return updated order information to re-build the grid. But Refresh/F5 will kill all user-entered info.
What else?
Is this problem common? How do you solve similar ones? What's the best practices?
It depends a lot on how you implement your objects/validation, but your option number 5 is probably the best idea. If AJAX isn't your thing, you can accomplish the same thing by writing the relevant data of already-added-but-not-saved entries to hidden fields.
In other words, the flow ends up something like this:
User enters an item.
Item is sent to the server and validated. The view is returned with the data entered by the user in hidden fields.
User enters a second item.
Item is sent to the server, and both items are validated. The view is returned with the data for both items in hidden fields.
etc.
So far as F5/Refresh killing entered data... In my experience this isn't too much of a problem. A more pressing concern is the back/forward buttons, which need to be managed with something like Really Simple History.
If you DO want to make the page continue to work after a refresh, you need to do one of the following:
Persist the records to the database, associated with the current user in some way.
Persist the records to session.
Persist the records to the query string.
These are the only storage locations available that persist through both redirection and refreshes.
If I were you, I would come up with something which resembles option 5. And since you say that you are comfortable with Ajax you can try this. But before you do this, you should move your validation logic outside the Order.Add() method. Maybe you can move it to another public function called Validate() which returns a bool. And you can still call the same Validate() in the Add() method, thereby doing the necessary validation before you add the order.
Try to do the validation on the client side. If you are using jQuery, you can use the jquery validate plugin. But, if this is not possible for some reason (such as when you need to validate stuff against a database). You should do your validation on the server side and just return a JSON object with the 'success' boolean flag and an optional message, just a way to mark that the data is valid. You would allow the user to add a new product only if the previous Order was valid.
And when the user hits finish send the product to the server and do the validation again, but persist the order in this round-trip.
Now, If I had a complete say in this, I wouldn't even go to the extent of doing validation whenever a product is added/edited. I would just do the validation whenever the customer hits finish. That would be the simplest solution. But, maybe I am missing something.
I have a simple web-site. Almost every action takes int toonId as an argument (toonId does not equate to the user: one user can own multiple toons).
At the moment I provide that value to every view from controller, and every link and submit button sends this value back to the controller.
This works. I am just looking for an easier way to accomplish this (AOP comes to mind).
I use cookies to persist "favorite/default toon", and this works fine (used with ActionAttribute, that takes toonId from cookie and passes is to the toonId argument, if toonId wasn't provided). But I want to support cookie-less sessions as well.
Question: What is an easy way to add ambient variable to the page, without passing it explicitly all over? Such that it would work with cookie-less browsers.
Is Viewstate a way to go (which isn't supposed to be in the MVC)?
Is server-side session a way to go?
This is exactly what session is meant for.
There is no page lifecycle in asp.net mvc, hence no viewstate.
I believe you can automatically pass query string parameters for use cookie-less browsers if you plug in your own implementation of IRouteHandler. I have not tried it though. I found an example of implementing IRouteHandler although it does not show how to implement the query string parameter functionality.
I'm not clear on what 'toonid' represents. If it's 'ambient per user session', then as womp stated, Session state would work well for this. However, if it's somethign that has a larger scope (e.g. a colleciton of toonids that are commonly accessed by all users) then teh Cache would be a better and more scalable strategy.