Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Should throw an exception when attempting to use TempData without TempDataProvider #5537

Closed
FSMaxB opened this issue Nov 17, 2016 · 17 comments
Closed
Assignees

Comments

@FSMaxB
Copy link

FSMaxB commented Nov 17, 2016

In my application I was trying to use TempData to pass data to another controller action across redirects, but at first I couldn't figure out why the data got lost every time on redirect.

After asking on StackOverlow, I figured out, that I have to manually register a TempDataProvider in order to make it work. So after adding services.AddSingleton<ITempDataProvider,SessionStateTempDataProvider>() to my Startup.cs, everything was working as expected.

I think TempData should throw an Exception when you try to use it without a TempDataProvider being configured. That Exception should tell the user, that a TempDataProvider is required.

@Eilon Eilon added this to the 1.2.0 milestone Nov 21, 2016
@Eilon
Copy link
Member

Eilon commented Nov 21, 2016

@kichalla I'm surprised we don't already have such an exception... was this always the case? Or did something regress at some point?

@kichalla
Copy link
Member

@FSMaxB Could you share how your Startup.cs looks like? Also which version of packages are you using (1.0.0 or 1.1.0)?
@Eilon the SessionStateTempDataProvider is a default service that we add, but this depends on Session so session services needs to be registered explicitly in the app though (not sure if this is being confused with the tempdata provider)
https://github.com/aspnet/Mvc/blob/rel/1.1.0/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs#L201

@Eilon
Copy link
Member

Eilon commented Nov 22, 2016

@kichalla yes, but the session services aren't registered by default, so I thought that we'd throw an exception at some point if you try to use TempData and the required services aren't there?

@FSMaxB
Copy link
Author

FSMaxB commented Nov 22, 2016

Startup.cs (some information removed):

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.ResponseCompression;
using System.IO.Compression;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

namespace SparePartManagement
{
    public class Startup
    {
        private static IConfiguration _configuration;

        public Startup(IHostingEnvironment environment)
        {
            var configurationBuilder = new ConfigurationBuilder()
                .SetBasePath(environment.ContentRootPath)
                .AddJsonFile("appsettings.defaults.json", optional: false)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile("appsettings.json.{environment.EnvironmentName}.json", optional: true);

            if (environment.IsDevelopment())
            {
                configurationBuilder.AddUserSecrets();
            }

            configurationBuilder.AddEnvironmentVariables();
            _configuration = configurationBuilder.Build();
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            // configuration
            services.AddOptions();
            services.Configure<EmailSettings>(settings => EmailSettings.Populate(_configuration, settings));
            services.Configure<DatabaseSettings>(settings => DatabaseSettings.Populate(_configuration, settings));
            services.Configure<HostingSettings>(settings => HostingSettings.Populate(_configuration, settings));
            services.Configure<RequestSettings>(settings => RequestSettings.Populate(_configuration, settings));

            //compression
            services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Fastest);
            services.AddResponseCompression(options =>
            {
                options.Providers.Add<GzipCompressionProvider>();
            });

            // session
            services.AddDistributedMemoryCache();
            services.AddSession();

            services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders()
                .AddRoleManager<ApplicationRoleManager>();

            services.AddMvc();

            services.AddDbContext<ApplicationDbContext>(options => options.UseNpgsql(databaseConnection));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime lifetime)
        {
            loggerFactory.AddConsole();

            app.UseResponseCompression();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseSession();
            app.UseIdentity();

            app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Home}/{action=Index}"
                    );
                }
            );
        }
    }
}

I had the problem initally with 1.0.0, but it didn't change after upgrading to 1.1.0. Session was configured, and before attempting to use TempData for the first time, that was actually what I used instead (the session directly, withouth TempData).

@kichalla
Copy link
Member

I had the problem initally with 1.0.0, but it didn't change after upgrading to 1.1.0. Session was configured, and before attempting to use TempData for the first time, that was actually what I used instead (the session directly, withouth TempData).

Ok, so you were using Session directly until you figured out to use TempData instead. The above Startup.cs looks fine and using TempData with the above startup should have worked fine. You were mentioning about explicitly adding the TempDataProvider, I do not see it in the above Startup.cs?

@eswise
Copy link

eswise commented Nov 22, 2016

I am having an interesting issue with TempData in my app. Granted I started with an Empty template since that is how I learn.

project.json contains:
"Microsoft.AspNetCore.Session": "1.1.0",
"Microsoft.Extensions.Caching.Memory": "1.1.0"

ConfigureServices() contains:
services.AddMemoryCache();
services.AddSession();
services.AddMvc();

I like to use TempData to send messages across redirects, so when they save something, it goes back to the index with a "Saved" message:

TempData["message"] = new DisplayMessage("Saved!", DisplayMessageType.Success);
return RedirectToAction("Index");

My _Layout page then does something like this:

@if (TempData["Message"] != null)
{
    DisplayMessage message = (DisplayMessage)TempData["Message"];
    <div class="container">
        <div class="col-xs-12">
            <div class="alert @message.GetMessageClass()">
                @message.Text
            </div>               
        </div>
    </div>
}

The behavior I am seeing is that if I set the TempData value and return View() (on the case of an error) it renders just fine. If I set the TempData value then RedirectToAction() it fails silently. Chrome browser does not update the location and I get this:

The localhost page isn’t working

localhost is currently unable to handle this request.
HTTP ERROR 500

@kichalla
Copy link
Member

@eswise Have you enabled logging and found any exceptions that are being logged?

@eswise
Copy link

eswise commented Nov 23, 2016

@kichalla
loggerFactory.AddConsole();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

VisualStudio doesn't break for any exception on this. I see nothing in debug output.

image

@eswise
Copy link

eswise commented Nov 23, 2016

Reproduce_TempData_Redirect.zip

@kichalla Here is the step by step screenshots of me using RedirectToAction() and having it fail silently and then switching it to return View() and having the message render properly.

@kichalla
Copy link
Member

Thanks for the repro. I will take a look. You seem to be debugging when using IIS Express. In this case you need to add Debug logger to see the output in the Debug output window.

@eswise
Copy link

eswise commented Nov 23, 2016

@kichalla
Ah, good tip on the debugger. I just started with core 2 days ago. Added the debug logger.

System.InvalidOperationException: The 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer' cannot serialize an object of type 'RemsLogic.UI.Models.DisplayMessage'.

That doesn't happen on return View(), only on return RedirectToAction()

@eswise
Copy link

eswise commented Nov 23, 2016

Looks like I ran head-first into this: #2276

This is different than the previous versions of MVC. Might need a warning label on that. :)

@eswise
Copy link

eswise commented Nov 23, 2016

Yep, confirmed. Changing the TempData assignment to a simple type (string) and it worked as expected. But why does it accept the complex type when you do not redirect? I would expect it to fail either way... I guess because if you don't redirect it doesn't try to serialize it...

@kichalla
Copy link
Member

kichalla commented Nov 23, 2016

I guess because if you don't redirect it doesn't try to serialize it...

Yes, the above is a reason you are not seeing this exception. In general once the keys of TempData are read they are marked for deletion and are not serialized.
RedirectToAction (and other RedirectTo methods) results are special in the sense that even if you read/get the value from TempData more than once the keys are not marked for deletion but are allowed to stay alive for the target action of the redirect to read.
In the case of Error, a ViewResult is being returned and reading TempData in this would cause the keys to be marked for deletion, and hence no serialization issue.

@FSMaxB
Copy link
Author

FSMaxB commented Nov 23, 2016

Ok, so you were using Session directly until you figured out to use TempData instead. The above Startup.cs looks fine and using TempData with the above startup should have worked fine. You were mentioning about explicitly adding the TempDataProvider, I do not see it in the above Startup.cs?

That was the Startup.cs where TempData didn't work. Before I manually added SessionStateTempDataProvider.

After that I added services.AddSingleton<ITempDataProvider,SessionStateTempDataProvider>(); directly after services.AddMvc. And then it worked.

@kichalla
Copy link
Member

I am unable to repro what you are seeing. Could you share a simple repro?

@FSMaxB
Copy link
Author

FSMaxB commented Nov 23, 2016

@kichalla: Nevermind, I cannot reproduce it myself anymore. Not even when checking out the commit where the issue initially occured. There must have been something quite strange going on either with the framework or in my head.

Also, if I don't register a Session, it fails because it cannot find the Session, as soon as I access TempData. (so it actually throws an exception as expected).

I will close this because it looks like the issue I described doesn't actually exist. Sorry for wasting your time. (I still wonder what that was though).

@FSMaxB FSMaxB closed this as completed Nov 23, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants