0

In the following example I have a controller that generates a key serial Id and encrypt some sensitive information like for example SSN. I have my functions in static classes to allow me to call them from anywhere in my project. both classes are running in the same context and run SP. I think the problem lies in there.

startup.cs

    public void ConfigureServices(IServiceCollection services)
            {
                services.Configure<MvcOptions>(options =>
                {
                    options.Filters.Add(new RequireHttpsAttribute());
                });
                services.AddDbContext<ApplicationDbContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("ARTNetCore")));

                services.AddIdentity<ApplicationUser, IdentityRole>(config =>
                {
                    config.SignIn.RequireConfirmedEmail = true;
                })
                    .AddEntityFrameworkStores<ApplicationDbContext>()
                    .AddDefaultTokenProviders();

                // Add application services.
                services.AddTransient<IEmailSender, AuthMessageSender>();
                services.AddTransient<ISmsSender, AuthMessageSender>();
                services.Configure<SMSoptions>(Configuration);
                services.AddMvc();
                // Configure Identity
                services.Configure<IdentityOptions>(options =>
                {
                    // Password settings
                    options.Password.RequireDigit = true;
                    options.Password.RequiredLength = 8;
                    options.Password.RequireNonAlphanumeric = false;
                    options.Password.RequireUppercase = true;
                    options.Password.RequireLowercase = false;

                    // Lockout settings
                    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
                    options.Lockout.MaxFailedAccessAttempts = 10;
                    options.Lockout.AllowedForNewUsers = true;


                    services.AddCookieAuthentication(o =>
                    {
                        o.LoginPath = "/Account/LogIn";
                        o.LogoutPath = "/Account/LogOut";
                    });


                    // User settings
                    options.User.RequireUniqueEmail = true;
                });
                services.Configure<AuthMessageSenderOptions>(Configuration);

            }
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {

            var options = new RewriteOptions()
               .AddRedirectToHttps();

            app.UseRewriter(options);

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

            app.UseStaticFiles();

            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            //Comment when migrating or demigrating

            //SeedData.Initialize(app.ApplicationServices);

        }

Controller is

private readonly ApplicationDbContext _context;

        public PatRegController(ApplicationDbContext context)
        {
            _context = context;
        }            
            [HttpPost]
            [ValidateAntiForgeryToken]
            public async Task<IActionResult> Create([Bind("FName,MName,LName,Dob,GenrId,StasId,NatyId,MarsId,CouyId,StaeId,CityId,OccnId,Email,SNN,PassNo,MobNo,LLine,MAdds,StrtNo,SDirn,AptNo,Locy,ALevl,PCode,Couy,ProeId")] PatReg patReg)
            {
                if (ModelState.IsValid)
                {
                    patReg.FileId = DbSerializerHandler.SerializeFileId(_context); 
                    patReg.SNN = DbEncryptionHandler.DynamicEncrypt(patReg.SNN, _context);
                    _context.Add(patReg);
                    await _context.SaveChangesAsync();
                    return RedirectToAction(nameof(Index));
                }
                return View(patReg);
            } 

Encryption static class is

 public static class DbEncryptionHandler
    {

        public static string DynamicEncrypt(string clearText, ApplicationDbContext _context)
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(clearText))
                {

                    List<EncKeys> Keys = new List<EncKeys>();
                    Keys = _context.EncKeys.FromSql("EncKeysSP").ToList();
                    string EncryptionKey = Keys[0].DynamicKey;
                    byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
                    using (Aes encryptor = Aes.Create())
                    {
                        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
                        encryptor.Key = pdb.GetBytes(32);
                        encryptor.IV = pdb.GetBytes(16);
                        using (MemoryStream ms = new MemoryStream())
                        {
                            using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
                            {
                                cs.Write(clearBytes, 0, clearBytes.Length);
                                cs.Close();
                            }
                            clearText = Convert.ToBase64String(ms.ToArray());
                        }
                    }
                }
                else
                {

                    clearText = null;

                }
            }
            catch (Exception ex)
            {
               throw;
            }

            return clearText;
        }
}

and Serializer static class is

 public static class DbSerializerHandler
    {
        public static Int64 SerializeFileId(ApplicationDbContext _context)
        {
            List<FileIdSeq> NewFileSeq = new List<FileIdSeq>();
            NewFileSeq = _context.FileIdSeq.FromSql("FileIdSeqSP").ToList();
            var FileID = NewFileSeq[0].LastSequence;
            return FileID;
        }
    }

When I try to save my data I get this error

An unhandled exception occurred while processing the request.

SqlException: New transaction is not allowed because there are other threads running in the session. System.Data.SqlClient.SqlConnection.OnError(SqlException exception, bool breakConnection, Action wrapCloseInAction)

The error returns on this controller line await _context.SaveChangesAsync();

What am I doing wrong here?

update I checked, I can run only one of these functions at a time but not both at the same time.

JSON
  • 1,583
  • 4
  • 31
  • 63
  • In where part of your code the error return? – Vijunav Vastivch Aug 11 '17 at 02:03
  • @reds, `await _context.SaveChangesAsync();` – JSON Aug 11 '17 at 02:04
  • @ScottChamberlain, its a private readonly,,, `private readonly ApplicationDbContext _context;`, I edited the question – JSON Aug 11 '17 at 02:07
  • Your code looks correct, I am out of ideas (maybe you have a middleware that is using the context in a separate thread that does not end it's transaction?). You can delete your old comments directed at me, I removed mine. – Scott Chamberlain Aug 11 '17 at 03:03
  • is it a better practice to initialize a different static context for each static class? – JSON Aug 11 '17 at 03:03
  • 1
    Instead of passing in the DbContext to your DbEncryptionHandler class you should provide it with the `Keys` data it needs. It doesn't make any sense for your encryption method to have a dependency on the DbContext. – Brad Aug 11 '17 at 05:57
  • @Brad just for a future reference, forget about this handler in particular, if I want to run two static classes using the same context, what shall I do ? – JSON Aug 11 '17 at 06:15

1 Answers1

1

The DbEncryptionHandler and DbSerializerHandler classes do not need to have a dependency on ApplicationDbContext, just the data they need from it.

public static class DbEncryptionHandler
{
   public static string DynamicEncrypt(string clearText, IEnumerable<EncKeys> keys)
   {
        ...
   }
}

public static class DbSerializerHandler
{
    public static Int64 SerializeFileId(IEnumerable<FileIdSeq> seq)
    {
        ...
    }
}

In your controller you can get the data from your context before you use your static classes.

public async Task<IActionResult> Create(PatReg patReg)
{
    if (ModelState.IsValid)
    {
        var seq = await _context.FileIdSeq.FromSql("FileIdSeqSP").ToListAsync();
        var keys = await _context.EncKeys.FromSql("EncKeysSP").ToListAsync();

        patReg.FileId = DbSerializerHandler.SerializeFileId(seq); 
        patReg.SNN = DbEncryptionHandler.DynamicEncrypt(patReg.SNN, keys);

        _context.Add(patReg);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(patReg);
} 
Brad
  • 4,493
  • 2
  • 17
  • 24
  • 1
    Works like charm, am I selecting correctly using `return seq.First(c=>c.SequenceDate!=null).LastSequence;` – JSON Aug 12 '17 at 03:42
  • While this fixes the problem, I wonder if someone could actually figure out why that's happening. In theory, the code should work. Something's wrong here and I'm concerned that this is an issue with EF Core. In which case, we should probably report the issue. @Ahmad Abu Maizar, what version of EFCore are you using? – Dealdiane Aug 13 '17 at 20:40
  • EF core2, VS 2017 preview Latest update, would u consider up vote might get more attention. – JSON Aug 13 '17 at 20:44
  • @Dealdiane I disagree. I believe the original problem was caused by making non async calls to the database in the static methods (because those static methods are not awaitable). My answer forces all database calls back into the controller where they could be awaited. I could have suggested to make the static methods return `Task` so they would be awaitable but felt the dependency on the DbContext was not necessary and was a bad design, which, as already evident, was causing problems when `SaveChangesAsync()` was being called. – Brad Aug 13 '17 at 23:35
  • @Brad the `.ToList()` call is a synchronous call isn't it? That means that in theory you don't need to `await` anything nor return a `Task`. That is spot on what I think the problem is though - the calls `.ToList()`, which should by synchronous, spins up threads and does not terminate it properly hence you get the exception. – Dealdiane Aug 13 '17 at 23:41
  • @Dealdiane if you believe that to be the case and is a problem with EF Core then let the EF Core team know on github. You might want to test your assumptions and offer proof first. – Brad Aug 13 '17 at 23:59
  • @Brad that's what I'm suggesting. I don't have time to do that (yet) so that's up for grabs at the moment. – Dealdiane Aug 14 '17 at 00:16
  • @Dealdiane, I think this is what you are alking about: [https://forums.asp.net/t/2126749.aspx?Getting+SqlException+while+await+_context+SaveChangesAsync+ – JSON Aug 14 '17 at 00:32