TL;DR: This post could be titled "Why do all my Insights calls get logged as 'A task was canceled.'?"
I am trying to use Application Insights to log errors from our ASP.NET web API 2 application but they are clearly not being logged. I have an Insights key and this is clearly being used because I can see REQUEST and DEPENDENCY entries, however there are no EXCEPTION or TRACE entries created by my custom code.
I am wondering if I am not seeing globally thrown exceptions because I don't have any controller-level exception handling - though the code below should, I believe, be enough to catch all exceptions and log them (when they are not more locally caught and handled)...
Global.asax.cs:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.Active.InstrumentationKey = EnvironmentHelper.InsightsKey;
GlobalConfiguration.Configure(WebApiConfig.Register);
}
protected void Application_Error(Object sender, EventArgs e)
{
Exception appException = Server.GetLastError();
var ai = new TelemetryClient();
ai.TrackException(appException);
}
}
WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Services.Replace(typeof(IExceptionLogger), new InsightsExceptionLogger());
GlobalConfiguration.Configuration.Filters.Add(new InsightsExceptionFilter());
}
}
InsightsExceptionLogger.cs:
public class InsightsExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
if (context != null && context.Exception != null)
{
var ai = new TelemetryClient();
ai.TrackException(context.Exception);
}
base.Log(context);
}
}
public class InsightsExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context != null && context.Exception != null)
{
var ai = new TelemetryClient();
ai.TrackException(context.Exception);
}
}
public override Task OnExceptionAsync(HttpActionExecutedContext context, CancellationToken cancellationToken)
{
if (context != null && context.Exception != null)
{
var ai = new TelemetryClient();
ai.TrackException(context.Exception);
}
return Task.FromResult(0);
}
}
Example Controller.cs:
[CookieAuthentication, CookieSlidingExpiration, InsightsExceptionFilter]
public class SomeController : ApiController
{
private TelemetryClient ai = null;
public SomeController()
{
TelemetryClient ai = new TelemetryClient();
}
[Route("api/v1/SomeEndpoint"), HttpGet]
public IHttpActionResult GetSomeEndpoint([FromUri]string aParameter)
{
ai.TrackTrace("test trace");
throw new Exception("test exception");
}
}
The first answer to this post is similar to my problem, but I am not seeing any trace statement or deliberate exception logged in Insights.
What I do see it a lot of is this log entry:
A task was canceled.
With this exception:
System.Threading.Tasks.TaskCanceledException:
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess
Though I do not understand how/why my deliberately thrown exceptions would be translated to this (if that is what is happening) or why my TrackTrace() calls are not appearing in the Insights log.
Under that exception I see this stack trace:
System.Threading.Tasks.TaskCanceledException:
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089)
at System.Web.Http.Controllers.ActionFilterResult+<ExecuteAsync>d__5.MoveNext (System.Web.Http, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089)
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089)
at System.Web.Http.Controllers.AuthenticationFilterResult+<ExecuteAsync>d__5.MoveNext (System.Web.Http, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089)
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089)
at System.Web.Http.Controllers.ExceptionFilterResult+<ExecuteAsync>d__6.MoveNext (System.Web.Http, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35)
It would appear that this whole mess has been the result of one incorrectly assigned variable. Very frustrating, but an easy fix. This was discovered when I removed the deliberate exception throwing from the controller methods and still logged the problem instead of trace messages - those should have been simple to receive. The faulty code is the constructor:
Example Controller.cs:
[CookieAuthentication, CookieSlidingExpiration, InsightsExceptionFilter]
public class SomeController : ApiController
{
private TelemetryClient ai = null;
public SomeController()
{
TelemetryClient ai = new TelemetryClient(); // WHY LOCAL???
}
[Route("api/v1/SomeEndpoint"), HttpGet]
public IHttpActionResult GetSomeEndpoint([FromUri]string aParameter)
{
ai.TrackTrace("test trace");
throw new Exception("test exception");
}
}
Of course, changing this to...
Example Controller.cs:
[CookieAuthentication, CookieSlidingExpiration, InsightsExceptionFilter]
public class SomeController : ApiController
{
private TelemetryClient ai = null;
public SomeController()
{
ai = new TelemetryClient();
}
[Route("api/v1/SomeEndpoint"), HttpGet]
public IHttpActionResult GetSomeEndpoint([FromUri]string aParameter)
{
ai.TrackTrace("test trace");
throw new Exception("test exception");
}
}
...solved the problem and I am now receiving trace statements as intended.
Related
Recently, I am occasionally facing an error at my AuthorizationPolicyProvider class:
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
private readonly AuthorizationOptions options;
public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
: base(options)
{
this.options = options.Value;
}
/// this method is supposed to be the place where the error arises
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
AuthorizationPolicy policy = await base.GetPolicyAsync(policyName);
if (policy == null)
{
policy = new AuthorizationPolicyBuilder()
.AddRequirements(new PermissionRequirement(policyName))
.Build();
this.options.AddPolicy(policyName, policy);
}
return policy;
}
}
Here is some of the error stack:
System.NullReferenceException:
at System.Collections.Generic.Dictionary`2.TryInsert (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
at System.Collections.Generic.Dictionary`2.set_Item (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
at Microsoft.AspNetCore.Authorization.AuthorizationOptions.AddPolicy (Microsoft.AspNetCore.Authorization, Version=3.1.6.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
.....
Based on the error stack, I have a guess that the error might be caused by multithreads problem of Dictionary, because the implementation of AuthorizationOptions use Dictionary, instead of ConcurrentDictionary:
namespace Microsoft.AspNetCore.Authorization
{
/// <summary>
/// Provides programmatic configuration used by <see cref="IAuthorizationService"/> and <see cref="IAuthorizationPolicyProvider"/>.
/// </summary>
public class AuthorizationOptions
{
// Why dont they use ConcurrentDictionary here
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
....
public AuthorizationPolicy GetPolicy(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
}
}
}
And I follow the guide of Microsoft in: link and add the custom AuthorizationPolicyProvider as Singleton:
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
So do you guys know why dont they use ConcurrentDictionary, is it a bug from Microsoft, or because of my implementation, thank you.
Do it yourself :
public class DynamicAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
private ConcurrentDictionary<string, AuthorizationPolicy> DynamicPolicyMap { get; } = new ConcurrentDictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
public DynamicAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) : base(options)
{
}
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
AuthorizationPolicy policy = await base.GetPolicyAsync(policyName);
if (policy == null)
{
return DynamicPolicyMap.GetOrAdd(policyName, CreateDynamicPolicy);
}
return policy;
}
private AuthorizationPolicy CreateDynamicPolicy(string policyName)
{
}
}
I would like to be able to catch all unhandled exceptions in one single place building a Blazor single page application.
Like using the "Current.DispatcherUnhandledException" in WPF applications.
This question is exclusively about client-side (webassembly) exception handling.
I am using Blazor version 3.0.0-preview8.19405.7
I have been searching for a solution, but it seems like it does not exist. On Microsofts documentation (https://learn.microsoft.com/en-us/aspnet/core/blazor/handle-errors?view=aspnetcore-3.0) there is a list of places errors may occur and a walk through on how to handle each one of them.
It believe there must be a more bullet proof way to catch all.
In .NET 6 there is component called ErrorBoundary.
Simple example:
<ErrorBoundary>
#Body
</ErrorBoundary>
Advanced Example:
<ErrorBoundary>
<ChildContent>
#Body
</ChildContent>
<ErrorContent Context="ex">
#{ OnError(#ex); } #*calls custom handler*#
<p>#ex.Message</p> #*prints exeption on page*#
</ErrorContent>
</ErrorBoundary>
For the global exception handling I see this as an option:
Create CustomErrorBoundary (inherit the ErrorBoundary) and override the OnErrorAsync(Exception exception).
Here is the sample of CustomErrorBoundary.
Useful links
Official docs
Some info in .NET 6 preview 4 blog post.
Tests for ErrorBoundary in dotnet repo (great sample).
PR on dotnet repo.
Simple usage of ErrorBoundary (youtube)
This works in v3.2+
using Microsoft.Extensions.Logging;
using System;
namespace UnhandledExceptions.Client
{
public interface IUnhandledExceptionSender
{
event EventHandler<Exception> UnhandledExceptionThrown;
}
public class UnhandledExceptionSender : ILogger, IUnhandledExceptionSender
{
public event EventHandler<Exception> UnhandledExceptionThrown;
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
if (exception != null)
{
UnhandledExceptionThrown?.Invoke(this, exception);
}
}
}
public class UnhandledExceptionProvider : ILoggerProvider
{
UnhandledExceptionSender _unhandledExceptionSender;
public UnhandledExceptionProvider(UnhandledExceptionSender unhandledExceptionSender)
{
_unhandledExceptionSender = unhandledExceptionSender;
}
public ILogger CreateLogger(string categoryName)
{
return new UnhandledExceptionLogger(categoryName, _unhandledExceptionSender);
}
public void Dispose()
{
}
public class UnhandledExceptionLogger : ILogger
{
private readonly string _categoryName;
private readonly UnhandledExceptionSender _unhandeledExceptionSender;
public UnhandledExceptionLogger(string categoryName, UnhandledExceptionSender unhandledExceptionSender)
{
_unhandeledExceptionSender = unhandledExceptionSender;
_categoryName = categoryName;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
// Unhandled exceptions will call this method
// Blazor already logs unhandled exceptions to the browser console
// but, one could pass the exception to the server to log, this is easily done with serilog
Serilog.Log.Fatal(exception, exception.Message);
}
public IDisposable BeginScope<TState>(TState state)
{
return new NoopDisposable();
}
private class NoopDisposable : IDisposable
{
public void Dispose()
{
}
}
}
}
}
Add this to Program.cs
var unhandledExceptionSender = new UnhandledExceptionSender();
var unhandledExceptionProvider = new UnhandledExceptionProvider(unhandledExceptionSender);
builder.Logging.AddProvider(unhandledExceptionProvider);
builder.Services.AddSingleton<IUnhandledExceptionSender>(unhandledExceptionSender);
Here is an example project implementing this solution.
Currently there is no central place to catch and handle client side exceptions.
Here is a quote from Steve Sanderson about it:
So overall, each component must deal with handling its own errors. If
you want, you could make your own ErrorHandlingComponentBase to
inherit from, and put a try/catch around all the lifecycle methods,
and have your own logic for displaying an "oh dear sorry I died" UI on
that component if anything went wrong. But it's not a feature of the
framework today.
I hope this will change in the future and I believe support should be backed into the framework.
For .NET 5 Blazor Server Side, this post Create Your Own Logging Provider to Log to Text Files in .NET Core worked for me. For my case, I have adapted this to catch unhandled exceptions to write into Azure storage table.
public class ExceptionLoggerOptions
{
public virtual bool Enabled { get; set; }
}
[ProviderAlias("ExceptionLogger")]
public class ExceptionLoggerProvider : ILoggerProvider
{
public readonly ExceptionLoggerOptions Options;
public ExceptionLoggerProvider(IOptions<ExceptionLoggerOptions> _options)
{
Options = _options.Value;
}
public ILogger CreateLogger(string categoryName)
{
return new ExceptionLogger(this);
}
public void Dispose()
{
}
}
public class ExceptionLogger : ILogger
{
protected readonly ExceptionLoggerProvider _exceptionLoggerProvider;
public ExceptionLogger([NotNull] ExceptionLoggerProvider exceptionLoggerProvider)
{
_exceptionLoggerProvider = exceptionLoggerProvider;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel == LogLevel.Error;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (false == _exceptionLoggerProvider.Options.Enabled) return;
if (null == exception) return;
if (false == IsEnabled(logLevel)) return;
var record = $"{exception.Message}"; // string.Format("{0} {1} {2}", logLevel.ToString(), formatter(state, exception), exception?.StackTrace);
// Record exception into Azure Table
}
}
public static class ExceptionLoggerExtensions
{
public static ILoggingBuilder AddExceptionLogger(this ILoggingBuilder builder, Action<ExceptionLoggerOptions> configure)
{
builder.Services.AddSingleton<ILoggerProvider, ExceptionLoggerProvider>();
builder.Services.Configure(configure);
return builder;
}
}
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStaticWebAssets().UseStartup<Startup>();
}).ConfigureLogging((hostBuilderContext, logging) =>
{
logging.AddExceptionLogger(options => { options.Enabled = true; });
});
To access exception you can use built-in ErrorBoundary component and access RenderFragment using Context attribute
<ErrorBoundary>
<ChildContent>
#Body
</ChildContent>
<ErrorContent Context="ex">
<h1 style="color: red;">Oops... error occured: #ex.Message </h1>
</ErrorContent>
</ErrorBoundary>
In the current Blazor webassembly version all unhandled exceptions are caught in an internal class and written to Console.Error. There is currently no way to catch them in a different way, but RĂ©mi Bourgarel shows a solution to be able to log them and/or take custom actions. See Remi's blog.
Simple logger to route them to an ILogger:
public class UnhandledExceptionLogger : TextWriter
{
private readonly TextWriter _consoleErrorLogger;
private readonly ILogger _logger;
public override Encoding Encoding => Encoding.UTF8;
public UnhandledExceptionLogger(ILogger logger)
{
_logger = logger;
_consoleErrorLogger = Console.Error;
Console.SetError(this);
}
public override void WriteLine(string value)
{
_logger.LogCritical(value);
// Must also route thru original logger to trigger error window.
_consoleErrorLogger.WriteLine(value);
}
}
Now in Program.cs add builder.Services.AddLogging... and add:
builder.Services.AddSingleton<UnhandledExceptionLogger>();
...
// Change end of Main() from await builder.Build().RunAsync(); to:
var host = builder.Build();
// Make sure UnhandledExceptionLogger is created at startup:
host.Services.GetService<UnhandledExceptionLogger>();
await host.RunAsync();
This will catch ALL errors.
App.razor
<ErrorBoundary>
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
<FocusOnNavigate RouteData="#routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</ErrorBoundary>
If you want to customize the message:
<ErrorBoundary>
<ChildContent>
... App
</ChildContent>
<ErrorContent Context="errorException">
<div class="blazor-error-boundary">
Boom!
</div>
</ErrorContent>
</ErrorBoundary>
Using the CustomErrorBoundary in the above example, and mudblazor. I made a custom error boundary component that displays the error in a snackbar popup.
In case someone else wants to do this.
CustomErrorBoundary.razor
#inherits ErrorBoundary
#inject ISnackbar Snackbar
#if (CurrentException is null)
{
#ChildContent
}
else if (ErrorContent is not null)
{
#ErrorContent(CurrentException)
}
else
{
#ChildContent
#foreach (var exception in receivedExceptions)
{
Snackbar.Add(#exception.Message, Severity.Error);
}
Recover();
}
#code {
List<Exception> receivedExceptions = new();
protected override Task OnErrorAsync(Exception exception)
{
receivedExceptions.Add(exception);
return base.OnErrorAsync(exception);
}
public new void Recover()
{
receivedExceptions.Clear();
base.Recover();
}
}
MainLayout.razor
#inherits LayoutComponentBase
#inject ISnackbar Snackbar
<MudThemeProvider IsDarkMode="true"/>
<MudDialogProvider />
<MudSnackbarProvider />
<MudLayout>
<MudAppBar>
<MudIconButton Icon="#Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="#((e) => DrawerToggle())" />
</MudAppBar>
<MudDrawer #bind-Open="#_drawerOpen">
<NavMenu/>
</MudDrawer>
<MudMainContent>
<CustomErrorBoundary>
#Body
</CustomErrorBoundary>
</MudMainContent>
</MudLayout>
#code {
bool _drawerOpen = true;
private void DrawerToggle()
{
_drawerOpen = !_drawerOpen;
}
}
I currently get my session from the globalasax as follows...
public class MvcApplication : HttpApplication
{
public static readonly ISessionFactory SessionFactory = NHibernateHelper.CreateSessionFactory();
public MvcApplication()
{
BeginRequest += delegate
{
if (!HttpContext.Current.Request.Url.AbsolutePath.StartsWith("/_cassette/"))
{
CurrentSession = SessionFactory.OpenSession();
CurrentSession.FlushMode = FlushMode.Auto;
}
};
EndRequest += delegate
{
if (CurrentSession != null)
{
CurrentSession.Flush();
CurrentSession.Dispose();
}
};
}
public static ISession CurrentSession
{
get { return (ISession) HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
I was looking at the Sharp Architecture Transaction attribute and a similar one http://weblogs.asp.net/srkirkland/archive/2009/09/03/asp-net-mvc-transaction-attribute-using-nhibernate.aspx but whats the best way of handling sessions in an MVC4 project to make use of none-implicit transactions ala http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions
I can easily wrap everything by adding the transaction/commit to the begin request/end request but the attribute method seems cleaner (actually handles errors); or should I be using a filter now?
What is the best practice for MVC4 with NHibernate?
Your current session handling has one serious problem (been there done that ;)). CurrentSession is static and hence it is shared among all concurrent requests. NHibernate's ISession is NOT thread safe (unlike ISessionFactory which IS thread safe).
NHibernate offers session contextes into which the session can be bound and after which the bound session can be acquired from session factory (.GetCurrentSession() -method). To be able to use CurrentSessionContext like in the next example you need to tell NHibernate which session context to use. For web applications WebSessionContext is good choice.
When I'm using MVC I write an action filter which takes care of the session handling. Here is an example (written for MVC 2):
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class TransactionAttribute : ActionFilterAttribute
{
public TransactionAttribute()
{
Order = 100;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CurrentSessionContext.Bind(NHibernateManager.SessionFactory.OpenSession());
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var session = CurrentSessionContext.Unbind(NHibernateManager.SessionFactory);
session.Close();
session.Dispose();
}
}
It shouldn't be too much of a problem to add transaction management also into the same filter. In OnActionExecuting-method you could open transaction with ISession's .BeginTransaction() and in OnActionExecuted you get the current transaction from ISession's Transaction-property which can then be committed and disposed.
There's another way to implement the "Session per request pattern" - httpModule.
public class NHibernateModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += context_BeginRequest;
context.EndRequest += context_EndRequest;
}
private static void context_BeginRequest(object sender, EventArgs e)
{
//use my session manager
ISession session = SessionManager.Instance.OpenSession();
CurrentSessionContext.Bind(session);
}
private static void context_EndRequest(object sender, EventArgs e)
{
ISessionFactory sessionFactory = SessionManager.Instance.SessionFactory;
ISession session = CurrentSessionContext.Unbind(sessionFactory);
if (session == null) return;
if (session.Transaction != null)
{
if (session.Transaction.IsActive)
{
//if there is an active session, commit it
session.Transaction.Commit();
}
else
{
//
session.Transaction.Rollback();
}
}
session.Close();
}
<configuration>
<!-- IIS 6 -->
<system.web>
<httpModules>
<add name="NHibernateModule" type="NHibernateModule"/>
</httpModules>
</system.web>
<!-- IIS 7 and Cassini. -->
<system.webServer>
<modules>
<add name="NHibernateModule" type="NHibernateModule"/>
</modules>
</system.webServer>
</configuration>
ActionFilterAttribute way has the question: how it will behave with a few actions in one HTTP request?
This pattern suggests that one NHibernate session be opened per HTTP request.
Following up to Ultor's answer, plus Ayende's "Refactoring toward frictionless & odorless code: What about transactions?" article, and, finally, because I have already set the dependency resolver of the application in Application_Start using the following:
DependencyResolver.SetResolver(new MyDependencyResolver())
I changed the TransactionAttribute class to be as follows:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class SessionAttribute : ActionFilterAttribute {
static readonly ISessionFactory SessionFactory = BuildSessionFactory();
static ISessionFactory BuildSessionFactory() {
return (ISessionFactory) DependencyResolver.Current.GetService(typeof (ISessionFactory));
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var sessionController = filterContext.Controller as SessionController;
if (sessionController == null)
return;
if (sessionController.NHibernateSession == null) {
sessionController.NHibernateSession = SessionFactory.OpenSession();
}
sessionController.NHibernateSession.BeginTransaction();
CurrentSessionContext.Bind(sessionController.NHibernateSession);
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var sessionController = filterContext.Controller as SessionController;
if (sessionController == null) return;
var session = CurrentSessionContext.Unbind(SessionFactory);
if (session == null) return;
if (session.Transaction != null) {
if (!session.Transaction.IsActive) return;
if (filterContext.Exception != null)
session.Transaction.Rollback();
else
session.Transaction.Commit();
}
session.Close();
session.Dispose();
}
}
And the base controller defined as such:
public class SessionController : Controller {
public ISession NHibernateSession { get; set; }
}
Now, persistence in my controller becomes as easy as:
[HttpGet, Session]
public ActionResult CreateOrUpdate(Guid id = new Guid()) {
var company = GetCompany(id);
if (company == null) throw new HttpException(404, "Not Found");
return View(company);
}
[HttpPost, ValidateAntiForgeryToken, Session]
public ActionResult CreateOrUpdate(Company passedInCompany) {
var company = NHibernateSession.Get<Company>(passedInCompany.Id);
if (company == null) throw new HttpException(404, "Not Found");
UpdateModel(company);
if (ModelState.IsValid) {
NHibernateSession.SaveOrUpdate(company);
return RedirectToAction("Index");
}
return View(company);
}
Company GetCompany(Guid id) {
Company company;
if (id == Guid.Empty) {
company = companyBuilder.Create();
} else {
company = NHibernateSession.Get<Company>(id);
NHibernateSession.Flush();
}
return company;
}
There are some good answers here but my recommendation is to use a dependency inject framework (I like Ninject) to implement session-per-request. This allows you to use constructor injection on the controllers to inject the ISession.
I'm trying to attempt a structure with Autofac on Wcf.
namespace WcfService1.Model
{
[DataContract(IsReference = true)]
public partial class Account
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Surname { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public Nullable<System.DateTime> CreateDate { get; set; }
}
}
Model>IAccounRepository.cs
1.
namespace WcfService1.Model
{
public interface IAccountRepository
{
IEnumerable<Account> GetAllRows();
bool AddAccount(Account item);
}
}
Model>AccounRepository.cs
2.
namespace WcfService1.Model
{
public class AccountRepository:IAccountRepository
{
private Database1Entities _context;
public AccountRepository()
{
if(_context == null)
_context =new Database1Entities();
}
public IEnumerable<Account> GetAllRows()
{
if (_context == null)
_context = new Database1Entities();
return _context.Account.AsEnumerable();
}
public bool AddAccount(Account item)
{
try
{
if (_context == null)
_context = new Database1Entities();
_context.Entry(item).State = EntityState.Added;
_context.Account.Add(item);
_context.SaveChanges();
return true;
}
catch (Exception ex)
{
var str = ex.Message;
return false;
}
}
}
}
DbConnection > EntityFramework + DbContext
IService1.cs
Code:
namespace WcfService1
{
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface IService1
{
[OperationContract]
IList<Account> GetAccounts();
[OperationContract]
bool AddAccount(Account item);
}
}
Service1.cs
Code:
namespace WcfService1
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1:IService1
{
private readonly IAccountRepository _repository;
public Service1(IAccountRepository repository)
{
_repository = repository;
}
public IList<Account> GetAccounts()
{
var items = _repository.GetAllRows().ToList();
return items;
}
public bool AddAccount(Account item)
{
item.CreateDate = DateTime.Now;
return _repository.AddAccount(item);
}
}
}
Service1.svc
Code:
<%# ServiceHost Language="C#"
Debug="true"
Service="WcfService1.Service1, WcfService1"
Factory="Autofac.Integration.Wcf.AutofacWebServiceHostFactory, Autofac.Integration.Wcf" %>
Global.asax.cs
Code:
protected void Application_Start(object sender, EventArgs e)
{
var builder = new ContainerBuilder();
builder.RegisterType< AccountRepository>().As< IAccountRepository>();
builder.RegisterType< Service1 >().As< IService1>();
AutofacHostFactory.Container = builder.Build();
}
I'm getting the following error, could not find a solution. What's my wrong.
Error Message :
Server Error in '/' Application.
The service 'WcfService1.Service1, WcfService1' configured for WCF is not registered with the Autofac container.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The service 'WcfService1.Service1, WcfService1' configured for WCF is not registered with the Autofac container.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[InvalidOperationException: The service 'WcfService1.Service1, WcfService1' configured for WCF is not registered with the Autofac container.]
Autofac.Integration.Wcf.AutofacHostFactory.CreateServiceHost(String constructorString, Uri[] baseAddresses) +667
System.ServiceModel.HostingManager.CreateService(String normalizedVirtualPath, EventTraceActivity eventTraceActivity) +2943
System.ServiceModel.HostingManager.ActivateService(ServiceActivationInfo serviceActivationInfo, EventTraceActivity eventTraceActivity) +88
System.ServiceModel.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath, EventTraceActivity eventTraceActivity) +1239
[ServiceActivationException: The service '/Service1.svc' cannot be activated due to an exception during compilation. The exception message is: The service 'WcfService1.Service1, WcfService1' configured for WCF is not registered with the Autofac container..]
System.Runtime.AsyncResult.End(IAsyncResult result) +454
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.End(IAsyncResult result) +413
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.ExecuteSynchronous(HttpApplication context, String routeServiceVirtualPath, Boolean flowContext, Boolean ensureWFService) +327
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.ExecuteSynchronous(HttpApplication context, Boolean flowContext, Boolean ensureWFService) +46
System.ServiceModel.Activation.HttpModule.ProcessRequest(Object sender, EventArgs e) +384
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +238
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +114
In addition to the other answers, you should make sure you're using the fully-qualified service name in the Service attribute of the ServiceHost element in your .svc file.
For example, instead of:
<%# ServiceHost Language="C#" Debug="true" Service="MoviesService.MoviesService" CodeBehind="MoviesService.svc.cs" %>
Use:
<%# ServiceHost Language="C#" Debug="true" Service="MoviesService.MoviesService, MoviesService" CodeBehind="MoviesService.svc.cs" %>
Source: http://jmonkee.net/wordpress/2011/09/05/autofac-wcfintegration-service-not-registered-with-the-autofac-container/
You should register the service as self, not as the interface.
builder.RegisterType< Service1 >().AsSelf();
Just Register the Service1 Like this builder.RegisterType<Service1>(); instead builder.RegisterType<Service1>().As<IService1>();
You should write in .svc file (Namespace1):
<%# ServiceHost Language="C#" Debug="true" Service="Namespace1.Service1, Namespace1"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" CodeBehind="Service1.svc.cs" %>
Give this a try:
var builder = new ContainerBuilder();
builder.Register(c => new AccountRepository()).As<IAccountRepository>();
builder.Register(c => new Service1(c.Resolve<IAccountRepository>())).AsSelf();
AutofacHostFactory.Container = builder.Build();
You should not use.
`builder.RegisterType< Service1 >().As'
but use RegisterType without extension methods
'builder.RegisterType();'
For me I was using a Project called 'WCF Service'
This by default gave me a name space called WCF_Service, and a assembly name of 'WCF Service'
None of the fixes worked until that space was removed.
Using event handlers in my Site-scoped feature's Feature Receiver, I'm adding my HttpHandler to my configuration (I'm new to this, so the code is a bit disjointed, as I've found it here and there).
public override void FeatureActivated(SPFeatureReceiverProperties properties) {
var site = (SPSite)properties.Feature.Parent;
var webApp = site.WebApplication;
if (!webApp.IsAdministrationWebApplication) {
var modification = new SPWebConfigModification("add[#name='SharePointNinjectHttpModule']", "configuration/system.web/httpModules");
modification.Owner = "addSharePointNinjectHttpModule";
modification.Sequence = 0;
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
modification.Value = #"<add name=""SharePointNinjectHttpModule"" type=""Foo.Bar.SharePointNinjectHttpModule,Foo.Bar, Version=1.0.0.0, Culture=neutral, PublicKeyToken=****************"" />";
webApp.WebConfigModifications.Add(modification);
try {
webApp.WebService.ApplyWebConfigModifications();
webApp.Update();
}
catch (SecurityException e) {
// todo ApplyWebConfigModifications throws "Access Denied" SecurityException when activating via Site Settings
}
}
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
var site = (SPSite)properties.Feature.Parent;
var webApp = site.WebApplication;
if (!webApp.IsAdministrationWebApplication) {
var oCollection = webApp.WebConfigModifications;
var iStartCount = oCollection.Count;
for (int c = iStartCount - 1; c >= 0; c--) {
SPWebConfigModification oModification = oCollection[c];
if (oModification.Owner == "addSharePointNinjectHttpModule") {
oCollection.Remove(oModification);
}
}
if (iStartCount > oCollection.Count) {
try {
webApp.WebService.ApplyWebConfigModifications();
webApp.Update();
}
catch (SecurityException e) {
// todo ApplyWebConfigModifications throws "Access Denied" SecurityException when deactivating via Site Settings
}
}
}
}
My SharePoint instance's web.config httpModules section when the feature is not active:
<httpModules>
</httpModules>
And when it is:
<httpModules>
<add name="SharePointNinjectHttpModule" type="Foo.Bar.SharePointNinjectHttpModule,Foo.Bar, Version=1.0.0.0, Culture=neutral, PublicKeyToken=****************" />
</httpModules>
So it seems like the feature receiver event handlers are doing their job (maybe I'm missing something?).
And here's the HttpModule:
using System;
using System.Web;
using Foo.Bar.Models;
using Ninject;
namespace Foo.Bar {
public class SharePointNinjectHttpModule : IHttpModule {
public void Init(HttpApplication context) {
if (Kernel == null) {
Kernel = new StandardKernel();
Kernel.Bind<IRepository>().To<Repository>();
}
}
public static IKernel Kernel { get; private set; }
public void Dispose() {}
private static IKernel GetKernel() {
IKernel result = new StandardKernel();
result.Bind<IRepository>().To<Repository>();
return result;
}
}
}
The Init() method of my HttpModule never fires. When should I expect it to fire, and why isn't that happening?
The following change to FeatureActivated() resolved the problem I was having:
var modification = new SPWebConfigModification("add[#name='SharePointNinjectHttpModule']", "configuration/system.webServer/modules");
I was injecting the module in the wrong section of my web.config.
Original: configuration/system.web/httpModules
Changed: configuration/system.webServer/modules
Once I made the above change, I was correctly adding the httpModule
Once I was correctly adding the httpModule, my Init() fired immediately.