I am experimenting with creating my own custom HTTP Context:
CustomHttpContext : HttpContextBase
{
public override HttpRequestBase Request { }
}
One thing i can't figure out is how to initialize the base class with
System.Web.HttpContext.Current
Does anyone have any ideas how i can initialise the custom context first with the Current Http then override certain Methods/Properties to serve my own purpose?
The simple answer is no, it's not possible. Also note that HttpContext does not inherit from HttpContextBase, instead, they both implement IServiceProvider. Finally, HttpContext is sealed, suggesting that the authors did not want people to do anything other than consume this class.
As you are no doubt annoyed by HttpContextBase has a parameterless constructor so does not even give you the option of instantiating it from the current request and response like HttpContext!
Let's use a 'decompiler' to take a look at the implementation of HttpContext.Current:
// System.Web.HttpContext
/// <summary>Gets or sets the <see cref="T:System.Web.HttpContext" /> object for the current HTTP request.</summary>
/// <returns>The <see cref="T:System.Web.HttpContext" /> for the current HTTP request.</returns>
public static HttpContext Current
{
get
{
return ContextBase.Current as HttpContext;
}
set
{
ContextBase.Current = value;
}
}
If we take a look at ContextBase.Current (from System.Web.Hosting.ContextBase):
// System.Web.Hosting.ContextBase
internal static object Current
{
get
{
return CallContext.HostContext;
}
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
set
{
CallContext.HostContext = value;
}
}
and CallContext (in System.Runtime.Messaging):
// System.Runtime.Remoting.Messaging.CallContext
/// <summary>Gets or sets the host context associated with the current thread.</summary>
/// <returns>The host context associated with the current thread.</returns>
/// <exception cref="T:System.Security.SecurityException">The immediate caller does not have infrastructure permission. </exception>
public static object HostContext
{
[SecurityCritical]
get
{
IllogicalCallContext illogicalCallContext = Thread.CurrentThread.GetIllogicalCallContext();
object hostContext = illogicalCallContext.HostContext;
if (hostContext == null)
{
LogicalCallContext logicalCallContext = CallContext.GetLogicalCallContext();
hostContext = logicalCallContext.HostContext;
}
return hostContext;
}
[SecurityCritical]
set
{
if (value is ILogicalThreadAffinative)
{
IllogicalCallContext illogicalCallContext = Thread.CurrentThread.GetIllogicalCallContext();
illogicalCallContext.HostContext = null;
LogicalCallContext logicalCallContext = CallContext.GetLogicalCallContext();
logicalCallContext.HostContext = value;
return;
}
LogicalCallContext logicalCallContext2 = CallContext.GetLogicalCallContext();
logicalCallContext2.HostContext = null;
IllogicalCallContext illogicalCallContext2 = Thread.CurrentThread.GetIllogicalCallContext();
illogicalCallContext2.HostContext = value;
}
}
We start to get a feel for how the HttpContext is being retrieved. It's being packaged in with the thread the current user started when they visted the website (which makes perfect sense!). Delving further we can see it also gets recreated per request (see below).
We can also see, at the interface layer, HttpContext.Current cannot be changed to point at your own HttpContext as the property is not virtual. It also uses many BCL classes that are private or internal so you can't simply copy most of the implementation.
What would be easier, and also less prone to any other issues would be to simply wrap HttpContext with your own CustomContext object. You could simply wrap HttpContext.Current in a BaseContext property, then have your own properties on the class (and use whatever session, database, or request based state storage mechanism you want to store and retrieve your own properties).
Personally, I'd use my own class for storing my own information, as it belongs to my application and user etc and isn't really anything to do with the http pipeline or request/response processing.
See also:
ASP.NET MVC : How to create own HttpContext
How is HttpContext being maintained over request-response
Just to add on a bit to dash's answer, you can also use the [ThreadStatic] attribute with some static property. Initialize it on BeginRequest, either by using global.cs or by writing your own HttpModule/HttpHandler.
How to create a web module:
http://msdn.microsoft.com/en-us/library/ms227673(v=vs.100).aspx
Thread static:
http://msdn.microsoft.com/en-us/library/system.threadstaticattribute.aspx
Related
I'm having trouble specifying two separate Authorization attributes on a class method: the user is to be allowed access if either of the two attributes are true.
The Athorization class looks like this:
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class AuthAttribute : AuthorizeAttribute {
. . .
and the action:
[Auth(Roles = AuthRole.SuperAdministrator)]
[Auth(Roles = AuthRole.Administrator, Module = ModuleID.SomeModule)]
public ActionResult Index() {
return View(GetIndexViewModel());
}
Is there a way to solve this or do I need to rethink my approach?
This is to be run in MVC2.
There is a better way to do this in later versions of asp.net you can do both OR and AND on roles. This is done through convention, listing multiple roles in a single Authorize will perform an OR where adding Multiple Authorize Attributes will perform AND.
OR example
[Authorize(Roles = "PowerUser,ControlPanelUser")]
AND Example
[Authorize(Roles = "PowerUser")]
[Authorize(Roles = "ControlPanelUser")]
You can find more info on this at the following link
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles
Multiple AuthorizeAttribute instances are processed by MVC as if they were joined with AND. If you want an OR behaviour you will need to implement your own logic for checks. Preferably implement AuthAttribute to take multiple roles and perform an own check with OR logic.
Another solution is to use standard AuthorizeAttribute and implement custom IPrincipal that will implement bool IsInRole(string role) method to provide 'OR' behaviour.
An example is here:
https://stackoverflow.com/a/10754108/449906
I've been using this solution in production environment for awhile now, using .NET Core 3.0. I wanted the OR behavior between a custom attribute and the native AuthorizeAttribute. To do so, I implemented the IAuthorizationEvaluator interface, which gets called as soon as all authorizers evaluate theirs results.
/// <summary>
/// Responsible for evaluating if authorization was successful or not, after execution of
/// authorization handler pipelines.
/// This class was implemented because MVC default behavior is to apply an AND behavior
/// with the result of each authorization handler. But to allow our API to have multiple
/// authorization handlers, in which the final authorization result is if ANY handlers return
/// true, the class <cref name="IAuthorizationEvaluator" /> had to be extended to add this
/// OR behavior.
/// </summary>
public class CustomAuthorizationEvaluator : IAuthorizationEvaluator
{
/// <summary>
/// Evaluates the results of all authorization handlers called in the pipeline.
/// Will fail if: at least ONE authorization handler calls context.Fail() OR none of
/// authorization handlers call context.Success().
/// Will succeed if: at least one authorization handler calls context.Success().
/// </summary>
/// <param name="context">Shared context among handlers.</param>
/// <returns>Authorization result.</returns>
public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
{
// If context.Fail() got called in ANY of the authorization handlers:
if (context.HasFailed == true)
{
return AuthorizationResult.Failed(AuthorizationFailure.ExplicitFail());
}
// If none handler called context.Fail(), some of them could have called
// context.Success(). MVC treats the context.HasSucceeded with an AND behavior,
// meaning that if one of the custom authorization handlers have called
// context.Success() and others didn't, the property context.HasSucceeded will be
// false. Thus, this class is responsible for applying the OR behavior instead of
// the default AND.
bool success =
context.PendingRequirements.Count() < context.Requirements.Count();
return success == true
? AuthorizationResult.Success()
: AuthorizationResult.Failed(AuthorizationFailure.ExplicitFail());
}
}
This evaluator will only be called if added to .NET service collection (in your startup class) as follows:
services.AddSingleton<IAuthorizationEvaluator, CustomAuthorizationEvaluator>();
In the controller class, decorate each method with both attributes. In my case [Authorize] and [CustomAuthorize].
I'm not sure how others feel about this but I wanted an OR behavior too. In my AuthorizationHandlers I just called Succeed if any of them passed. Note this did NOT work with the built-in Authorize attribute that has no parameters.
public class LoggedInHandler : AuthorizationHandler<LoggedInAuthReq>
{
private readonly IHttpContextAccessor httpContextAccessor;
public LoggedInHandler(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LoggedInAuthReq requirement)
{
var httpContext = httpContextAccessor.HttpContext;
if (httpContext != null && requirement.IsLoggedIn())
{
context.Succeed(requirement);
foreach (var req in context.Requirements)
{
context.Succeed(req);
}
}
return Task.CompletedTask;
}
}
Supply your own LoggedInAuthReq. In startup inject these in services with
services.AddAuthorization(o => {
o.AddPolicy("AadLoggedIn", policy => policy.AddRequirements(new LoggedInAuthReq()));
... more here
});
services.AddSingleton<IAuthorizationHandler, LoggedInHandler>();
... more here
And in your controller method
[Authorize("FacebookLoggedIn")]
[Authorize("MsaLoggedIn")]
[Authorize("AadLoggedIn")]
[HttpGet("anyuser")]
public JsonResult AnyUser()
{
return new JsonResult(new { I = "did it with Any User!" })
{
StatusCode = (int)HttpStatusCode.OK,
};
}
This could probably also be accomplished with a single attribute and a bunch of if statements. It works for me in this scenario. asp.net core 2.2 as of this writing.
I have a service that depends on other services for example
OrderProvider(IOrderService service) {
}
That is a direct dependant so having it in the constructor is fine.
There are some other methods that require other services, so I have been handling these with properties, for example, I may need to get the Stock for an Order:
private IStockService _stockService;
public IStockService StockService { get { return _stockService ?? (_stockService = new StockService()); } }
Stock GetStock(string orderNumber) {
return StockService.Get(orderNumber);
}
As you can see, in my old way of doing things the property was only instantiated when requested.
Now I have moved to autofac I would like to set up a similar method, i.e. If a request is only for an Order then only the OrderProvider and the OrderService will be instantiated, but if they request the Stock then all 3 will be instantiated.
I really hope that makes sense.
Autofac allows you to request a Lazy<T> closed over the desired type for scenarios like yours that require delayed instantiation. The first time the Lazy<T>'s value is accessed is when the actual instance will be created.
Example Code
private Lazy<IStockService> _lazyStockService;
public IStockService StockService
{
get { return _lazyStockService.Value; }
}
public OrderProvider( IOrderService service, Lazy<IStockService> lazyStockService )
{
_service = service;
_lazyStockService = lazyStockService;
}
Here's a link to Autofac's docs on this topic
Here's a link to the docs for Lazy<T>
Trying to access the HttpContext.Current in a method call back so can I modify a Session variable, however I receive the exception that HttpContext.Current is null. The callback method is fired asynchronously, when the _anAgent object triggers it.
I'm still unsure of the solution to this after viewing similar questions on SO.
A simplified version of my code looks like so:
public partial class Index : System.Web.UI.Page
protected void Page_Load()
{
// aCallback is an Action<string>, triggered when a callback is received
_anAgent = new WorkAgent(...,
aCallback: Callback);
...
HttpContext.Current.Session["str_var"] = _someStrVariable;
}
protected void SendData() // Called on button click
{
...
var some_str_variable = HttpContext.Current.Session["str_var"];
// The agent sends a message to another server and waits for a call back
// which triggers a method, asynchronously.
_anAgent.DispatchMessage(some_str_variable, some_string_event)
}
// This method is triggered by the _webAgent
protected void Callback(string aStr)
{
// ** This culprit throws the null exception **
HttpContext.Current.Session["str_var"] = aStr;
}
[WebMethod(EnableSession = true)]
public static string GetSessionVar()
{
return HttpContext.Current.Session["str_var"]
}
}
Not sure if necessary but my WorkAgent class looks like so:
public class WorkAgent
{
public Action<string> OnCallbackReceived { get; private set; }
public WorkAgent(...,
Action<string> aCallback = null)
{
...
OnCallbackReceived = aCallback;
}
...
// This method is triggered when a response is received from another server
public BackendReceived(...)
{
...
OnCallbackReceived(some_string);
}
}
What happens in the code:
Clicking a button calls the SendData() method, inside this the _webAgent dispatches a message to another server and waits for reply (in the mean time the user can still interact with this page and refer to the same SessionID). Once received it calls the BackendReceived() method which, back in the .aspx.cs page calls the Callback() method.
Question:
When the WorkAgent triggers the Callback() method it tries to access HttpContext.Current which is null. Why is that the case when if I continue on, ignoring the exception, I can still obtain the same SessionID and the Session variable using the ajax returned GetSessionVar() method.
Should I be enabling the aspNetCompatibilityEnabled setting?Should I be creating some sort of asynchronous module handler? Is this related to Integrated/Classic mode?
Here's a class-based solution that is working for simple cases so far in MVC5 (MVC6 supports a DI-based context).
using System.Threading;
using System.Web;
namespace SomeNamespace.Server.ServerCommon.Utility
{
/// <summary>
/// Preserve HttpContext.Current across async/await calls.
/// Usage: Set it at beginning of request and clear at end of request.
/// </summary>
static public class HttpContextProvider
{
/// <summary>
/// Property to help ensure a non-null HttpContext.Current.
/// Accessing the property will also set the original HttpContext.Current if it was null.
/// </summary>
static public HttpContext Current => HttpContext.Current ?? (HttpContext.Current = __httpContextAsyncLocal?.Value);
/// <summary>
/// MVC5 does not preserve HttpContext across async/await calls. This can be used as a fallback when it is null.
/// It is initialzed/cleared within BeginRequest()/EndRequest()
/// MVC6 may have resolved this issue since constructor DI can pass in an HttpContextAccessor.
/// </summary>
static private AsyncLocal<HttpContext> __httpContextAsyncLocal = new AsyncLocal<HttpContext>();
/// <summary>
/// Make the current HttpContext.Current available across async/await boundaries.
/// </summary>
static public void OnBeginRequest()
{
__httpContextAsyncLocal.Value = HttpContext.Current;
}
/// <summary>
/// Stops referencing the current httpcontext
/// </summary>
static public void OnEndRequest()
{
__httpContextAsyncLocal.Value = null;
}
}
}
To use it can hook in from Global.asax.cs:
public MvcApplication() // constructor
{
PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute);
EndRequest += new EventHandler(OnEndRequest);
}
protected void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
HttpContextProvider.OnBeginRequest(); // preserves HttpContext.Current for use across async/await boundaries.
}
protected void OnEndRequest(object sender, EventArgs e)
{
HttpContextProvider.OnEndRequest();
}
Then can use this in place of HttpContext.Current:
HttpContextProvider.Current
There may be issues as I currently do not understand this related answer. Please comment.
Reference: AsyncLocal (requires .NET 4.6)
When using threads or an async function, HttpContext.Current is not available.
Try using:
HttpContext current;
if(HttpContext != null && HttpContext.Current != null)
{
current = HttpContext.Current;
}
else
{
current = this.CurrentContext;
//**OR** current = threadInstance.CurrentContext;
}
Once you set current with a proper instance, the rest of your code is independent, whether called from a thread or directly from a WebRequest.
Please see the following article for an explanation on why the Session variable is null, and possible work arounds
http://adventuresdotnet.blogspot.com/2010/10/httpcontextcurrent-and-threads-with.html
quoted from the from the article;
the current HttpContext is actually in thread-local storage, which explains why child threads don’t have access to it
And as a proposed work around the author says
pass a reference to it in your child thread. Include a reference to HttpContext in the “state” object of your callback method, and then you can store it to HttpContext.Current on that thread
I have a web application where many components are registered using .LifestylePerWebRequest(), now I've decided to implement Quartz.NET, a .NET job scheduling library, which executes in separate threads, and not the Request thread.
As such, HttpContext.Current yields null. My services, repositories, and IDbConnection were instanced so far using .LifestylePerWebRequest() because it made it easier to dispose of them when the requests ended.
Now I want to use these components in both scenarios, during web requests I want them to remain unaffected, and in non-request contexts I want them to use a different Lifestyle, I figure I can handle the disposing myself, but how should I go about it for choosing a lifestyle for the components based on the current context?
Currently I register services (for example), like this:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
I figure I should be using some kind of extension method but I just don't see it..
You should use Hybrid Lifestyle from castleprojectcontrib.
An hybrid lifestyle is one that actually blends two underlying lifestyles: a main lifestyle and a secondary lifestyle. The hybrid lifestyle first tries to use the main lifestyle; if it's unavailable for some reason, it uses the secondary lifestyle. This is commonly used with PerWebRequest as the main lifestyle: if the HTTP context is available, it's used as the scope for the component instance; otherwise the secondary lifestyle is used.
Don't use the same components. In fact, in most scenarios I've seen the "background processing" doesn't even make sense to be in the web process to begin with.
Elaborating based on the comments.
Shoehorning background processing in the web pipeline is compromising your architecture to save a few $ on a EC2 instance. I would strongly suggest to think about this again, but I digress.
My statements still stands, even if you're putting both components in the web process they are two different components used in two different contexts and should be treated as such.
I've had a very similar problem recently - I wanted to be able to run initialisation code based off my container in the Application startup, when HttpContext.Request does not yet exist. I didn't find any way of doing it, so I modified the source of the PerWebRequestLifestyleModule to allow me to do what I wanted. Unfortunately it didn't seem possible to make this change without recompiling Windsor - I was hoping I would be able to do it in an extensible way so I could continue to use the main distribution of Windsor.
Anyway, to make this work, I modified the GetScope function of the PerWebRequestLifestyleModule so that if it was NOT running in a HttpContext (or if HttpContext.Request throws an exception, like it does in Application_Start) then it will look for a Scope started from the container instead. This allows me to use my container in Application_Start using the following code:
using (var scope = container.BeginScope())
{
// LifestylePerWebRequest components will now be scoped to this explicit scope instead
// _container.Resolve<...>()
}
There's no need to worry about explicitly disposing of things, because they will be disposed when the Scope is.
I've put the full code for the module below. I had to shuffle a couple of other things around within this class for it to work, but it's essentially the same.
public class PerWebRequestLifestyleModule : IHttpModule
{
private const string key = "castle.per-web-request-lifestyle-cache";
private static bool allowDefaultScopeOutOfHttpContext = true;
private static bool initialized;
public void Dispose()
{
}
public void Init(HttpApplication context)
{
initialized = true;
context.EndRequest += Application_EndRequest;
}
protected void Application_EndRequest(Object sender, EventArgs e)
{
var application = (HttpApplication)sender;
var scope = GetScope(application.Context, createIfNotPresent: false);
if (scope != null)
{
scope.Dispose();
}
}
private static bool IsRequestAvailable()
{
if (HttpContext.Current == null)
{
return false;
}
try
{
if (HttpContext.Current.Request == null)
{
return false;
}
return true;
}
catch (HttpException)
{
return false;
}
}
internal static ILifetimeScope GetScope()
{
var context = HttpContext.Current;
if (initialized)
{
return GetScope(context, createIfNotPresent: true);
}
else if (allowDefaultScopeOutOfHttpContext && !IsRequestAvailable())
{
// We're not running within a Http Request. If the option has been set to allow a normal scope to
// be used in this situation, we'll use that instead
ILifetimeScope scope = CallContextLifetimeScope.ObtainCurrentScope();
if (scope == null)
{
throw new InvalidOperationException("Not running within a Http Request, and no Scope was manually created. Either run from within a request, or call container.BeginScope()");
}
return scope;
}
else if (context == null)
{
throw new InvalidOperationException(
"HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net");
}
else
{
EnsureInitialized();
return GetScope(context, createIfNotPresent: true);
}
}
/// <summary>
/// Returns current request's scope and detaches it from the request context.
/// Does not throw if scope or context not present. To be used for disposing of the context.
/// </summary>
/// <returns></returns>
internal static ILifetimeScope YieldScope()
{
var context = HttpContext.Current;
if (context == null)
{
return null;
}
var scope = GetScope(context, createIfNotPresent: true);
if (scope != null)
{
context.Items.Remove(key);
}
return scope;
}
private static void EnsureInitialized()
{
if (initialized)
{
return;
}
var message = new StringBuilder();
message.AppendLine("Looks like you forgot to register the http module " + typeof(PerWebRequestLifestyleModule).FullName);
message.AppendLine("To fix this add");
message.AppendLine("<add name=\"PerRequestLifestyle\" type=\"Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor\" />");
message.AppendLine("to the <httpModules> section on your web.config.");
if (HttpRuntime.UsingIntegratedPipeline)
{
message.AppendLine(
"Windsor also detected you're running IIS in Integrated Pipeline mode. This means that you also need to add the module to the <modules> section under <system.webServer>.");
}
else
{
message.AppendLine(
"If you plan running on IIS in Integrated Pipeline mode, you also need to add the module to the <modules> section under <system.webServer>.");
}
#if !DOTNET35
message.AppendLine("Alternatively make sure you have " + PerWebRequestLifestyleModuleRegistration.MicrosoftWebInfrastructureDll +
" assembly in your GAC (it is installed by ASP.NET MVC3 or WebMatrix) and Windsor will be able to register the module automatically without having to add anything to the config file.");
#endif
throw new ComponentResolutionException(message.ToString());
}
private static ILifetimeScope GetScope(HttpContext context, bool createIfNotPresent)
{
var candidates = (ILifetimeScope)context.Items[key];
if (candidates == null && createIfNotPresent)
{
candidates = new DefaultLifetimeScope(new ScopeCache());
context.Items[key] = candidates;
}
return candidates;
}
}
Ok, I figured out a very clean way to do this!
First of all we'll need an implementation of IHandlerSelector, this can select a handler based on our opinion on the matter, or remain neutral (by returning null, which means "no opinion").
/// <summary>
/// Emits an opinion about a component's lifestyle only if there are exactly two available handlers and one of them has a PerWebRequest lifestyle.
/// </summary>
public class LifestyleSelector : IHandlerSelector
{
public bool HasOpinionAbout(string key, Type service)
{
return service != typeof(object); // for some reason, Castle passes typeof(object) if the service type is null.
}
public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
{
if (handlers.Length == 2 && handlers.Any(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest))
{
if (HttpContext.Current == null)
{
return handlers.Single(x => x.ComponentModel.LifestyleType != LifestyleType.PerWebRequest);
}
else
{
return handlers.Single(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest);
}
}
return null; // we don't have an opinion in this case.
}
}
I made it so the opinion is very limited on purpose. I'll be having an opinion only if there are exactly two handlers and one of them has PerWebRequest lifestyle; meaning the other one is probably the non-HttpContext alternative.
We need to register this selector with Castle. I do so before I start registering any other components:
container.Kernel.AddHandlerSelector(new LifestyleSelector());
Lastly I wish I had any clue as to how I could copy my registration to avoid this:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerThread()
);
If you can figure out a way to clone a registration, change the lifestyle and register both of them (using either container.Register or IRegistration.Register), please post it as an answer here! :)
Update: In testing, I need to uniquely name the identical registrations, I did so like this:
.NamedRandomly()
public static ComponentRegistration<T> NamedRandomly<T>(this ComponentRegistration<T> registration) where T : class
{
string name = registration.Implementation.FullName;
string random = "{0}{{{1}}}".FormatWith(name, Guid.NewGuid());
return registration.Named(random);
}
public static BasedOnDescriptor NamedRandomly(this BasedOnDescriptor registration)
{
return registration.Configure(x => x.NamedRandomly());
}
I don't know whats happening behind the scenes in .LifestylePerWebRequest(); but this is what I do for "Context per request" scenarios:
Check HttpContext for session and if exists pull the context from .Items.
If it doesn't exist pull your context from System.Threading.Thread.CurrentContext.
Hope this helps.
I use the following, static class to access the data context in my application
public static class DataContext
{
internal const string _contextDataKey = "dataContext";
/// <summary>
/// Returns a unique data context that lives for the duration of the request, which can be from ASP.NET or a WCF service
/// </summary>
/// <returns>The entity data model context for the current request</returns>
public static EntityDataModelContext GetDataContext()
{
IPersistanceContainer state;
if (HttpContext.Current != null)
{
state = new AspNetPersistanceContainer();
}
else if (OperationContext.Current != null)
{
state = new WcfPersistanceContainer();
}
else
{
state = new StaticPersistanceContainer(); // this container is thread-unsafe.
}
EntityDataModelContext edm = state.Get<EntityDataModelContext>(_contextDataKey);
if (edm == null)
{
edm = new EntityDataModelContext();
state.Store(_contextDataKey, edm);
}
return edm;
}
}
Forget about the other containers, which are for WCF and Console application simple-tests respectively, here's the ASP.NET container:
internal class AspNetPersistanceContainer : IPersistanceContainer
{
public T Get<T>(string key) where T : class
{
if (HttpContext.Current.Items.Contains(key))
return (T)HttpContext.Current.Items[key];
return null;
}
public void Store(string key, object value)
{
HttpContext.Current.Items[key] = value;
}
}
When I need to access the context I just invoke DataContext.GetDataContext() and do my DB-accessing, I never add any using statements.
If I add a using statement, the context is good for one use, and the next time I try to use it, it's disposed of. Raising an exception.
If I don't, like right now, it makes me kind of unhappy, I feel like it's not the right thing to do either, not disposing of it.
So I was wondering what would be the correct thing to do here.
Is this design flawed, and should I abandon it altogether?
Should I just figure out a way to re-create the context whenever it's disposed of?
Should I just leave the design as is, and that's fine?
Maybe the design is "fine enough", are there any books that you'd recommend I read on the subject? I feel like my skills on back-end architecture are rather on the lacking side.
In an asp.net application one solution can be like this :
Create your context when a request begins
Dispose it when the request ends
Here's an article that discuss this approach (for NHibernate session management but it's almost the same for EF )