Setting the context's connection name dependin

2019-09-06 10:34发布

问题:

This used to be a Web Forms application and I'm rewriting it in MVC.

The connection name is stored in the user's details and depending on the user, I need to pass a different connection name.

In WebForms this was solved by setting a Session variable in Global.asax and then every time the context is needed, I'd create the context in a using statement, passing the Session variable in the context constructor like this:

using (IAccountContext db = new MainContext(Session["cn"]) { }

In MVC I'm declaring the context as a field:

public class ManageController : BaseController
{    
    private readonly IAccountContext _db = new MainContext(ConnectionName);

    // other actions

}

and I can't pass the non-static Session variable. I tried using a BaseController that the rest of the controllers would inherit and tried initializing the ConnectionName in the constructor and in the Initialize method but neither worked

public class BaseController : Controller
{
    public BaseController()
    {
        using (var db = new UserDbContext())
        {
            ConnectionName = (db.Users.Find(User.Identity.GetUserId())).ConnectionString; 
        }
    }

    public static string ConnectionName { get; set; }

    protected override void Initialize(RequestContext requestContext)
    {
        using (var db = new UserDbContext())
        {
            ConnectionName = (db.Users.Find(User.Identity.GetUserId())).ConnectionString;
        }
        base.Initialize(requestContext);
    }
}

I'm trying to avoid using a using clause in every single Action and rely on MVC to dispose the context in the Dispose method.

Any ideas on how to make this work?

EDIT:

Francesc Castells's answer looks promising, but when using the BaseController the User.Identity.GetUserID() call throws a NullReference exception. If the ManageController inherits from the original Controller it works just fine. Am I forgetting to initialize something else?

This is the Stacktrace

[NullReferenceException: Object reference not set to an instance of an object.]
   TestProj.Web.Controllers.BaseController..ctor() in E:\TestProj.Web\Controllers\BaseController.cs:14
   TestProj.Web.Controllers.ManageController..ctor() in E:\TestProj.Web\Controllers\ManageController.cs:60

[TargetInvocationException: Exception has been thrown by the target of an invocation.]
   System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
   System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +119
   System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +232
   System.Activator.CreateInstance(Type type, Boolean nonPublic) +83
   System.Activator.CreateInstance(Type type) +11
   System.Web.Mvc.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType) +55

[InvalidOperationException: An error occurred when trying to create a controller of type 'TestProj.Web.Controllers.ManageController'. Make sure that the controller has a parameterless public constructor.]
   System.Web.Mvc.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType) +178
   System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) +76
   System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) +88
   System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) +194
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +50
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +48
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +103
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

回答1:

Use RequestContext in Initialize() method.

public class BaseController : Controller
{
    protected string _connectionName;
    protected override void Initialize(RequestContext requestContext)
    {
        if(requestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            using (var db = new UserDbContext())
            {
                _connectionName = db.Users.Find(
                    requestContext.HttpContext.User.Identity.GetUserId())
                    .ConnectionString;
            }
        }
        base.Initialize(requestContext);
    }
}

Now you could simply inherit your controllers:

public class ManageController : BaseController
{
    private IAccountContext _db          

    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);
        _db = new MainContext(_connectionName);
    }

    public ActionResult MyAction()
    {
        // now you have access _db variable
    }

    protected override void Dispose(bool disposing)
    {
        if(disposing)
        {
            _db.Dispose();
        }
        base.Dispose(disposing);
    }
}


回答2:

Create the MainContext in the constructor, after the BaseController constructor has been executed and the ConnectionName initialized.

public class ManageController : BaseController
{    
    private readonly IAccountContext _db;

    public ManageController()
    {
          // Here the base constructor has already been executed, so ConnectionName has the right value
         _db = new MainContext(ConnectionName);
    }

   // other actions

 }

But I would suggest a different approach: Use dependency injection with an IoC, for example Unity. Register your IAccountContext with an InjectionFactory which would be a simple method that would run the code you already have to get the connection string based on the User.Identity.GetUserId() and create the AccountContext passing the discovered connection string.

I don't know if you are familiar with what I am saying or not. If you need it I can write more detailed instructions.