Session gets lost when enabling OutputCache - c#

I have a (working) MVC-application that uses Session properties on multiple parts:
return httpContext.Session[SPContextKey] as SharePointAcsContext;
(ignore that this is sharepoint; This problem isn't be SP-specific)
This works fine until I try to enable Outputcaching:
[OutputCache (Duration =600)]
public ActionResult Select() {
DoSelect();
}
When the content is cached, httpContext.Session becomes NULL.
Is there a way to keep the Session data and also use caching?

I found the solution myself. It took a while until I came to the conclusion that - if the data is cached - there shouldn't be any individual code at all that is run. Cause that should be the main purpose of the cache: Don't run any code when the data is cashed.
That led me to the conclusion that the code causing the problem must be run before the cache. And so the "bad boy" was easy to find. Another attribute (in this case an AuthorizeAttribute) that is before the OutputCache-Attribute in the code is still run when caching applies but cannot access the Session:
[Route("{id}")]
[UserAuth(Roles =Directory.GroupUser)]
[JsonException]
[OutputCache(Duration = 600)]
public ActionResult Select()
{
DoSelect();
}
Putting the UserAuth-Attribute BELOW the OutputCache-Attribute solved the problem

Related

Blazor WASM - Controller not found when making a PostAsJsonAsync Request

I am building a WASM app for the first time, and have been following tutorials.
The Solution I have is composed of 3 projects created by the wizard (Client, Server and Shared).
I am having trouble when making the following request from the index page:
var msg = await Http.PostAsJsonAsync<u001_000_001>("api/u001_000_001", userRec);
If (msg.IsSuccessStatusCode) ClearUserScr();
In the Server project, I have a Controllers folder with a controller named u001-000-001Controller (although the class name in the file is u001_000_001Controller). The relevant lines of code from the controller class are as follows:
[ApiController]
[Route("api/[controller]")]
public class u001_000_001Controller : ControllerBase
{
[HttpPost]
public async Task<u001_000_001> Post([FromBody] u001_000_001 create)
{
EntityEntry<u001_000_001> user = await db.u001_000_001.AddAsync(create);
await db.SaveChangesAsync();
return user.Entity;
}
}
The HttpClient is registered using the builder.HostEnvironment.baseAddress as the Uri in the Client Program.cs file.
The Shared folder contains the handler called u001-000-001 (class name u001_000_001).
I have tried all the different combinations I can think of in terms of changing the names in the actual call, and nothing works. I keep getting the same "not found - HTTP 400' error.
I would sincerely appreciate help from experienced eyes to see if there is a simple mistake I'm making or if there's something more serious I'm missing. Many thanks in advance for your time.
Many hours of research later, the error was found to be in the fields being fed initially into the handler, rather than anything happening with the actual HttpClient request or the controller.
Although I found that the Http/1.1 400 Bad request error could be generated by a range of issues, I would highly recommend reviewing the structure of the data being input as a first step, since this was overlooked in my case.
Clarification of Issue and Solution:
I have a process for creating new user logins, and the goal of the HttpClient PostAsJsonAsync request was to send new account details to the database. However one of the fields in the user record is a PIN number, and as this is not chosen by the new user in the first registration step, it was left as null in the code.
Keeping it null was fine for the code, but the Controller expects data to be input for all fields, and will not accept PostAsJsonAsync calls where any of the fields are left null.
The solution in my case was to set a temporary value, and then make the PostAsJsonAsync request with all of the fields filled and sent through the request.
I wish to thank the professionals who commented with potential solutions, as they helped to improve my code.

ASP.NET MVC 5 TempData and Session gets cleared on RedirectToAction [duplicate]

I have an interesting problem with the TempData object not passing values to another controller.
I set TempData["Enroll"] in the Enroll Controller's HttpPost method to an Enroll Model. I then read the TempData["Enroll"] object in the Register Controller's HttpGet method, but is empty/null.
I need to persist all of this data across 3 controllers.
Any thoughts?
Here is a code Snippet
//EnrollController.cs
[HttpPost]
public ActionResult Index(EnrollModel model)
{
// ...
TempData["EnrollModel"] = model;
return RedirectToAction("Index", "Register");
}
// RegisterController.cs
public ActionResult Index(string type)
{
RegisterModel model = new RegisterModel();
EnrollModel enrollModel = TempData["EnrollModel"] as EnrollModel;
model.ClientType = enrollModel.ClientType;
// ...
}
I've had an issue where TempData got lost during the redirect on my local machine.
I've checked web.config sessionState Setting which was InProc and therefore no problem.
It turned out that I got another setting in web.config, which was taken from production system. It looked like this:
<httpCookies requireSSL="true" />
After turning the requireSSL to false TempData workes fine.
I had the same problem today.
In this link some guys explain that RedirectAction method returns a HTTP 302 status to the browser, which causes the browser to make a new request and clear the temp, but I tried returning HTTP methods 303 (which is what the RedirectAction should be returning) and 307 also, and it didn't solve anything.
The only way of fixing the issue of TempData in my case was changing the sessionState directive of web.config to use StateServer instead of the default InProc. i.e:
<system.web>
<sessionState mode="StateServer" cookieless="AutoDetect" timeout="30" stateConnectionString="tcpip=localhost:42424"></sessionState>
...
</system.web>
I figured this out when reading this Greg Shackles' article, where he explains how TempData works and build a custom TempDataProvider, which rely on MongoDB database instead of session like the default one.
Hope that my 4 hours researching helps someone to not waste their time.
I have come across these sorts of limitations with TempData before. I found it unrealiable and sporadic at best.
You need to consider what you are trying to achieve. If you do need to store data, in practice the best place to do this is in a db (or store of sorts) it might seem a bit overkill but that is their purpose.
Two other points:
Someone can hit your RegisterController Index method without going to the other before, in which case your code would break.
If you are doing a multiple wizard style process, why not store the data in its partial state in the db, and complete the process only on the last screen? In this way no matter, where they stop/start or pick it up again, you will always know where they are in the process.
Save your results to a cache or db or pass in as posts/querystrings between your controllers. TempData is cleared by several things including a worker process reset which could surely happen between steps.
In addition your code above could get a null ref exception:
EnrollModel enrollModel = TempData["EnrollModel"] as EnrollModel;
if(enrollModel==null)
{
//handle this model being null
}
model.ClientType = enrollModel.ClientType;
Fixing your issue as you have it above though is tough without seeing all code and knowing if there is anything else that may/may not refer to it.

MVC solution becomes unresponsive

I am building a solution using asp.net MVC and it is my first time doing so.
The problem
After the solution has been running for some time the controllers become unresponsive. The client side works fine, and when calling a controller using ajax, the controller begins excecuting, but whenever it hits the first line it stops excecution. The ajax call is still waiting for a responce, and I am able to make a new request (that also stops). It does not throw any errors, it just stalls. This happens both when debugging or "start-without-debugging".
Entity framework is used within some of these controller methods for database calls, if it has anything to do with the error.
I am suspecting that one of these things must be done, but I am not certain at all:
Is this only an issue because I am using localhost?
Are there some settings describing maximal runtime? Is there a specific thing
I must do whenever a controller is called?
Does the database has anything to do with the stalling?
I am not sure what code to attach, since this error seems to happen after some time, and not specific to a controller or method. Let me know if there is something you want to see.
Edit
I'm adding one ajax call as example, but note that the ajax call is working. It is the controllers (Not only RemoveTest-controller) that stalls and only after debugging for some time.
$.ajax({
type: "POST",
url: "/RemoveTest/RemoveByPlacement",
data: { input: someInput},
dataType: "json",
success: function (data) {
alert(data);
},
error: function () {
alert("Error");
}
});
Edit 2
I should maybe say that it is not only happening when using ajax. The problem also happens when opening a new page, since this also needs execution from a controller.
UPDATE
This may be an issue with the database. I have inserted var a = 10; to my controller, and this is getting executed. But it stops after trying to execute the line after.
[HttpPost]
public JsonResult RemoveById(string id)
{
var a = 10;
TestReciving test = db.TestRecevings.FirstOrDefault(x => x.Id == id);
Placement placement = db.Placements.FirstOrDefault(x => x.TestRecivingIdRef == id);
...
}
UPDATE
I did what Mark Homer said. Even though this had to be fixed, it did not solve my problem. When running in IE I get a HTML1300 (navigation occurred) message when changing page, but when it halts and I try to open the file I get "http://localhost:51140/Account/Login could not be opened". It seems to me that IIS Express stops responding, even though it keeps running.
The controller looks like this now:
[HttpPost]
public JsonResult RemoveById(string id)
{
using (var db = new DatabaseContext())
{
TestReciving test = db.TestRecevings.FirstOrDefault(x => x.Id == id);
Placement placement = db.Placements.FirstOrDefault(x => x.TestRecivingIdRef == id);
...
}
}
So I finally got it to work. The issue was a two-headed giant which was why I had troubles pinning it down.
First issue
Mark Homer said to use using when I used the database. I did this and while it solved a subset of the issue, it did not solve it completely. But from this day I will remember to use using.
Second issue
I was not able to track it down to something specific, but I created a new project and moved my files into it (except the auto generated once). Since doing this have have not been able to reproduce the problem. I tried using GitHub to find differences in the two solutions, but only to find that the non-working project had different framework versions. It really bugs me, that I can not pin down the reason, but at least it works.
Thanks for your responses, guys.

Output caching, VaryByParam/VaryByCustom

OK, there seem to be no end to the number of articles that sing the praises of output caching and how much it will speed up your website. I have now read hundreds of articles and Q&As on the topic, but I still can't seem to make it work. (I think output caching might be stealing my soul)
My requirements are pretty simple, I have 3 pages that I would like to cache based on parameters: Home, Results, and Details. I also have a small area of the page that needs to be varied by user. I also need to cache to a central repository and I have chosen redis to hold my data. I should also mention that this is still an old web forms app.
My original approach was to attempt to supply my own custom string using the "VaryByCustom" option. The Microsoft page seems to make this look simple:
https://msdn.microsoft.com/en-us/library/5ecf4420.aspx
This requires placing an overridden method "GetVaryByCustomString" in the global.asax file. The problem is that most of the examples show using variables from HttpContext that are always available (Browser, Browser Version etc.) and even though Microsoft and others seem to suggest this as the preferred "cache by custom string" method I can't find any working examples that allow the page to define the string. I have seen a few examples of people using session and claiming that it works (session is null for me) and context.user (some people say that user is null) but I don't see any practical way to deliver a string except by using Response.Cache.SetVaryByCustom("my_custom_string"). After a day of strugling with implementation in the main project I decided to build an isolated project for testing/proof of concept. In this project I was able to get my custom string working as long as I passed it as the string to SetVaryByCustom. The problem is that this doesn't really match any examples I've seen. The examples show "SomeKey" with "SomeValue" being returned by GetVaryByCustomString. What worked for me was essentially "SomeValue" with "SomeValue". Here was my code:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if (arg != null)
{
return arg;
}
return base.GetVaryByCustomString(context, arg);
}
This worked the first time on the page, after setting the value of a dropdown which created a postback, but never after that. So the "OnselectedItemChanged" fired once and created a new set of cache entries based on the selection, but it never fired again. I tried modifying just about every cache parameter I could to make it work but no matter what series of settings I attempted (set cache declaratively, set caching in code, tried various combinations of VaryByParams, adding location, etc) I was never successful.
After attempting for 2 days to get this to work I decided it was time for a different approach. I have gone back to a more traditional/accepted approach.
I created 3 cache profiles in my web.config:
<caching>
<cache disableExpiration="false" />
<outputCache defaultProvider="RedisOutputCacheProvider" enableOutputCache="true" enableFragmentCache="true" sendCacheControlHeader="true">
<providers>
<add name="RedisOutputCacheProvider"
type="LAC.OutputCacheProvider, LAC"
connectionString="192.168.XX.XX:6379,connectTimeout=10000"
dbNumber="5"
keyPrefix="LAC:OutputCache:" />
</providers>
</outputCache>
<outputCacheSettings>
<outputCacheProfiles>
<add name="Home" varyByParam="*" varyByCustom="user" duration="86400"/>
<add name="Results" varyByParam="*" varyByCustom="user" duration="7200" />
<add name="Details" varyByParam="*" varyByCustom="user" duration="7200"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
So each page has a custom vary by user string that should help me cache a new page for an authenticated user vs an anonymous one.
Then I added a GetVaryByCustomString that looks like:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
switch (arg)
{
case "user":
var identity = context.User.Identity.Name != "" ? context.User.Identity.Name : "Anonymous";
return string.Format("IsAuthenticated={0},Identity={1}", context.Request.IsAuthenticated, identity);
}
return string.Empty;
}
I am as close as I have ever been to having this working but it's still not 100%. The Home page works fine, the details page works fine, but the results page never caches. The results page has a filter containing 12 different dropdown lists. So it needs to store version of the page based on selections. I added a Timestamp to the top of all of my pages so I could see if the page was caching. The time on the results page changes every time I hit control-F5, which is not true on the other 2 pages. No cache entries are ever created and GetVaryByCustomString is never called. I have triple checked the code for some sort of "Turn cacheability Off" setting but as far as I can tell there is no code anywhere in the page that disables the cache. My global.asax file inherits from the correct class, my custom ouputcache provider seems to be adding the correct entries for the 2 pages that work. Of course now with VaryByParam="*" the key can be as long as the content thanks to viewstate (which has to be left on) BLECH!
So essentially I have caching set on three pages that are similar and one doesn't work. I have no idea where to look next. I am hoping someone like #kevinmontrose who really has a handle on this sort of stuff will take pity on me and give me a shove in a direction that will lead me to a solution.
UPDATE:
I have opened a case with Microsoft.
have you been setting VaryByParam? i forget the details at the moment but i recall getting stuck before finding out that it is mandatory. (in every case i think.(?)) i think duration is also required. (i'm referring to directives in the .aspx page.)
rereading your question i saw the issue: "I can't find any working examples that allow the page to define the string." i was rereading some articles and i'm pretty sure that's the wrong approach.
i haven't tested all of this but for ex, if you take the standard example:
<%# OutputCache Duration="10" VaryByParam="None" VaryByCustom="minorversion" %>
what seems to be happening is that you have just set the string to look for, and that's it.
when a request comes in, you check the HTTP request header - the Vary header, which is what you set in the page - and if it exists you do what you want.
<%# Application language="C#" %>
<script runat="server">
public override string GetVaryByCustomString(HttpContext context,
string arg)
{
// arg is your custom 'Vary' header.
if(arg == "minorversion")
{
return "Version=" +
context.Request.Browser.MinorVersion.ToString();
}
return base.GetVaryByCustomString(context, arg);
}
</script>
i just did a quick test of the basic example and added a label that is updated on page load with the current time. i refreshed the live page several times and the time did not change until 10 seconds had passed. so it definitely works. i think the key is that you set the value and you're done, but you can work with it if you want to.
... on second thought, i think you create the header in the page, and return the value from the global.asax. where?? i think it's sent to the caching system which decides if a new or cached page should be sent. in the example above the browser's minor version is checked and set and since there is a cached version available (after first load) it sends that, until 10 secs are up.
the docs for VaryByCustom is in a section for 'caching different versions of a page' so that's what this is doing. if someone with a different browser shows up, the browser version is checked - then the caching system checks to see if a version of the page for that browser version is available. if not, create a new page; if so, send the cached version.

ASP.NET MVC 3 + Razor Error: Child actions are not allowed to perform redirect actions

So I saw this question here on SO, but it hasn't really solved this problem for me.
I have an ASP.NET MVC 3 + Razor app running on IIS5 on my dev pc, and then IIS6 for my dev web server. Everything worked great until I deployed it. I am bin deploying everything, with no problems on that front (that I can tell).
Now I am getting this Child actions are not allowed to perform redirect actions error on my page. I am not sure how to pinpoint where it is failing.
I'm using #Html.Action to pull in some drop down boxes with data:
public ActionResult Hierarchy()
{
List<Store> storeList = DBService.getStores();
if (DBService.Error != null)
return RedirectToError(DBService.Error);
List<Department> deptList = DBService.getDepts();
if (DBService.Error != null)
return RedirectToError(DBService.Error);
VM_Hierarchy hierarchy = new VM_Hierarchy(storeList, deptList);
return View(hierarchy);
}
If I remove the #Html.Action line, the page will render. It will then break if I do an AJAX request to a controller action such as this:
[HttpPost]
public ActionResult InventoryList(string fromDate, string toDate)
{
List<Inventory> inventories = DBService.getInventories(fromDate, toDate);
if (DBService.Error != null)
return RedirectToAction("Error");
return View(inventories);
}
If this isn't correct, how am I supposed to redirect to an error page or dictate what view gets returned on a post? Any help is appreciated. Thanks.
It's probably because you're getting errors from your DB service when it's deployed, which you aren't getting locally. This is causing it to try and redirect from a child action, which you can't do.
You might want to try getting the drop-down data in your main action, and put it all in a ViewModel, so you aren't using child actions. If this fails, you can redirect to your heart's content. For the AJAX, you'll need to handle the errors on the client and do something sensible, you can't just return a redirection from an AJAX call.
This question has some further info:
Why are Redirect Results not allowed in Child Actions in Asp.net MVC 2
if you put in the child action
(ControllerContext.ParentActionViewContext.Controller as System.Web.Mvc.Controller).Response.Redirect(Url.Action("Action", "Controller")); it will redirect from the childaction.
I had a similar problem but for the life of me, I couldn't figure out where the redirection was coming from. Once I figured it out, I thought I'd post an answer in case it happens to anyone else.
If the source of the redirection is not apparent from the imperative code, e.g.:
[AuthoriseThis]
public ActionResult Details(SomethingModel something)
{
return PartialView(something);
}
Look at the declarative code!
In my case, it was an authorisation problem, so the attribute was causing the redirection, and my eyes were filtering out the existence of the attribute.

Categories