Is there a way to globally catch all unhandled errors in a Blazor single page application? - error-handling

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;
}
}

Related

Caching odata Web Api

I am developing an OData API for my Asp.net core application and i want to implement caching on this.
The problem is all my endpoints will be IQueryable with a queryable services with no execution at all. so i can't implement any caching on service level
Controller
public class TagsController : ODataController
{
private readonly ITagService _tagService;
private readonly ILogger<TagsController> _logger;
public TagsController(ITagService tagService, ILogger<TagsController> logger)
{
_tagService = tagService;
_logger = logger;
}
[HttpGet("odata/tags")]
[Tags("Odata")]
[AllowAnonymous]
[EnableCachedQuery]
public ActionResult<IQueryable<Tag>> Get()
{
try
{
return Ok(_tagService.GetAll());
}
catch (Exception ex)
{
_logger.LogError(ex, "Some unknown error has occurred.");
return BadRequest();
}
}
}
So I tried to apply an extension on EnableQuery attribute to add the caching implementation on it. so i added the following
public class EnableCachedQuery : EnableQueryAttribute
{
private IMemoryCache _memoryCache;
public EnableCachedQuery()
{
_memoryCache = new MemoryCache(new MemoryCacheOptions());
}
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
//var url = GetAbsoluteUri(actionContext.HttpContext);
var path = actionContext.HttpContext.Request.Path + actionContext.HttpContext.Request.QueryString;
//check cache
if (_memoryCache.TryGetValue(path, out ObjectResult value))
{
actionContext.Result = value;
}
else
{
base.OnActionExecuting(actionContext);
}
}
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
return;
var path = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
var cacheEntryOpts = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(15));
base.OnActionExecuted(context);
_memoryCache.Set(path, context.Result, cacheEntryOpts);
}
}
the first request completed successfully and retrieved the data correctly with filters and queries applied. then when tried to add the data to cache the context.Result holds the ObjectResult and then in the second request which should be cached the value was there but with an error in executing which means that the cached value is not the final output value that should be passed to the Result
Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'ApplicationDbContext'.
============================
Update:
public class ApplicationDbContext : IdentityDbContext<User, Account, Session>, IApplicationDbContext
{
public ApplicationDbContext(
DbContextOptions options,
IApplicationUserService currentUserService,
IDomainEventService domainEventService,
IBackgroundJobService backgroundJob,
IDomainEventService eventService,
IDateTime dateTime) : base(options, currentUserService, domainEventService, backgroundJob, dateTime) { }
public DbSet<Tag> Tags => Set<Tag>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
var entityTypes = builder.Model.GetEntityTypes()
.Where(c => typeof(AuditableEntity).IsAssignableFrom(c.ClrType))
.ToList();
foreach (var type in entityTypes)
{
var parameter = Expression.Parameter(type.ClrType);
var deletedCheck = Expression.Lambda
(Expression.Equal(Expression.Property(parameter, nameof(AuditableEntity.Deleted)), Expression.Constant(false)), parameter);
type.SetQueryFilter(deletedCheck);
}
builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
builder.ApplySeedsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
}

Exception handling and redirecting from view component

How can I implement exception handling in my view component?
Wrapping the logic from my action method into try/catch blocks doesn't catch any exceptions thrown within a view component itself, and I don't want the app to stop functioning regardless of any errors. This is what I'm doing so far and trying to accomplish:
Action Method
public IActionResult LoadComments(int id)
{
try
{
return ViewComponent("CardComments", new { id });
}
catch (SqlException e)
{
return RedirectToAction("Error", "Home");
}
}
To reiterate, this does not catch a SqlException that occurs inside the view component itself, and thus it fails to redirect.
View Component
public class CardCommentsViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(int id)
{
try
{
IEnumerable<CardCommentData> comments = await DbHelper.GetCardCommentData(id);
return View(comments);
}
catch (SqlException e)
{
//Redirect from here if possible?
}
}
}
Can I accomplish this from the controller's action method? If not, how can I redirect from the view component itself? I've tried researching this problem and came up empty. Any information would be helpful.
You can try to redirect to another page using HttpContextAccessor.HttpContext.Response.Redirect:
public class CardCommentsViewComponent : ViewComponent
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CardCommentsViewComponent( IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<IViewComponentResult> InvokeAsync(int id)
{
try
{
IEnumerable<CardCommentData> comments = await DbHelper.GetCardCommentData(id);
return View(comments);
}
catch (SqlException e)
{
_httpContextAccessor.HttpContext.Response.Redirect("/About");
return View(new List<CardCommentData>());
}
}
}
Register in DI :
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
But the preferred way is using global exception handler /filter to trace the exception and redirect to related error page :
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-2.2

Custom implementation of ILogger

In my project I often add prefixes to my log messages.
Currently I am doing this with
logger.LogDebug(prefix + " some message");
I thought it would be a good way to implement a custom logger where I set the prefix and the logger itself attaches it every time it logs something.
So I created my custom logger class and implemented the ILogger interface. But I do not understand how to use the
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
method to add the prefix (which is a member of the custom logger class).
My full code is:
public class CustomLogger : ILogger
{
private readonly ILogger _logger;
private string _logPrefix;
public CustomLogger(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_logPrefix = null;
}
public ILogger SetLogPrefix(string logPrefix)
{
_logPrefix = logPrefix;
return this;
}
public IDisposable BeginScope<TState>(TState state)
{
return _logger.BeginScope(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return _logger.IsEnabled(logLevel);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_logger.Log(logLevel, eventId, state, exception, formatter);
}
}
I think you should not call a _logger in a custom logger.
It would be a circular call on runtime and the result would be "prefix: prefix: prefix: prefix: prefix: prefix: prefix: prefix: ..."
Simply, you can create a simple logger and implement a log writter such as Console, database writter, log4net, ...
Now first, you should change your custom logger like below:
public class CustomLogger : ILogger
{
private readonly string CategoryName;
private readonly string _logPrefix;
public CustomLogger(string categoryName, string logPrefix)
{
CategoryName = categoryName;
_logPrefix = logPrefix;
}
public IDisposable BeginScope<TState>(TState state)
{
return new NoopDisposable();
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
string message = _logPrefix;
if (formatter != null)
{
message += formatter(state, exception);
}
// Implement log writter as you want. I am using Console
Console.WriteLine($"{logLevel.ToString()} - {eventId.Id} - {CategoryName} - {message}");
}
private class NoopDisposable : IDisposable
{
public void Dispose()
{
}
}
}
The second step, create a logger provider:
public class LoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new CustomLogger(categoryName, "This is prefix: ");
}
public void Dispose()
{
}
}
The third step, in Configure from Startup.cs:
loggerFactory.AddProvider(new MicroserviceLoggerProvider());
Personally, I think It's not a good way to do that, "prefix" will be duplicated a lot. Why don't you use Log Scopes instead?
public IActionResult GetById(string id)
{
TodoItem item;
using (_logger.BeginScope("Message attached to logs created in the using block"))
{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
}
return new ObjectResult(item);
}
Output
info: TodoApi.Controllers.TodoController[1002]
=> RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById (TodoApi) => Message attached to logs created in the using block
Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
=> RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById (TodoApi) => Message attached to logs created in the using block
GetById(0) NOT FOUND
Currently, you can't change the logging template, it's the limitation of built-in basic logging in Asp.net Core. For more powerful one, you can try Serilog, keep using ILogger interface and change some line of code in program.cs class
You should also look at this Benefits of Structured Logging vs basic logging
Implement an extensions for adding prefix to log records.
public static class LogExtensions
{
public static void PrefixLogDebug(this ILogger logger, string message, string prefix = "Edward", params object[] args)
{
logger.LogDebug($"{prefix} {message}");
}
}
Useage:
_log.PrefixLogDebug("Log From Prefix extension");
_log.PrefixLogDebug("Log From Prefix extension", "New Prefix");

How to correctly handle errors in ASP.NET Core middleware?

I've created a HandlerMiddleware-class, which executes a handler for a specific URL.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace FailTest
{
public interface IHttpHandler
{
bool IsReusable { get; }
void ProcessRequest(HttpContext context);
}
public abstract class HandlerMiddleware<T>
where T : IHttpHandler
{
private readonly RequestDelegate _next;
public HandlerMiddleware()
{ }
public HandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await SyncInvoke(context);
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.Message);
throw;
}
}
public Task SyncInvoke(HttpContext context)
{
try
{
// IHttpHandler handler = (IHttpHandler)this;
T handler = System.Activator.CreateInstance<T>();
handler.ProcessRequest(context);
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.Message);
throw;
}
return Task.CompletedTask;
}
} // End Abstract Class HandlerMiddleware
}
Now I have a class which implements a handler, e.g.
[HandlerPath("/treedata", "GET,POST")]
public class SomeFailingTask
: HandlerMiddleware<SomeFailingTask>, IHttpHandler
{
public SomeFailingTask() : this(null)
{ }
public SomeFailingTask(RequestDelegate next) : base(next)
{ }
bool IHttpHandler.IsReusable
{
get { throw new System.NotImplementedException(); }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
// Do something in DB and return result as JSON
// e.g. SQL with invalid syntax, or a missing parameter
}
}
It works fine as long as there are no errors in ProcessRequest.
But when an exception is thrown in ProcessRequest, it finishes the request with HTTP 200 OK.
What am I doing wrong ?
I get the exception, in both Invoke and SyncInvoke, but the HTTP request always finished as if everything was fine...

Is it possible to use one generic/abstract service in ServiceStack?

I am developing a (hopefully) RESTful API using ServiceStack.
I noticed that most of my services look the same, for example, a GET method will look something like this:
try
{
Validate();
GetData();
return Response();
}
catch (Exception)
{
//TODO: Log the exception
throw; //rethrow
}
lets say I got 20 resources, 20 request DTOs, so I got about 20 services of the same template more or less...
I tried to make a generic or abstract Service so I can create inheriting services which just implement the relevant behavior but I got stuck because the request DTOs weren't as needed for serialization.
Is there any way to do it?
EDIT:
an Example for what I'm trying to do:
public abstract class MyService<TResponse,TRequest> : Service
{
protected abstract TResponse InnerGet();
protected abstract void InnerDelete();
public TResponse Get(TRequest request)
{
//General Code Here.
TResponse response = InnerGet();
//General Code Here.
return response;
}
public void Delete(TRequest request)
{
//General Code Here.
InnerDelete();
//General Code Here.
}
}
public class AccountService : MyService<Accounts, Account>
{
protected override Accounts InnerGet()
{
throw new NotImplementedException();//Get the data from BL
}
protected override void InnerDelete()
{
throw new NotImplementedException();
}
}
To do this in the New API we've introduced the concept of a IServiceRunner that decouples the execution of your service from the implementation of it.
To add your own Service Hooks you just need to override the default Service Runner in your AppHost from its default implementation:
public virtual IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new ServiceRunner<TRequest>(this, actionContext); //Cached per Service Action
}
With your own:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new MyServiceRunner<TRequest>(this, actionContext); //Cached per Service Action
}
Where MyServiceRunner is just a custom class implementing the custom hooks you're interested in, e.g:
public class MyServiceRunner<T> : ServiceRunner<T> {
public override void OnBeforeExecute(IRequestContext requestContext, TRequest request) {
// Called just before any Action is executed
}
public override object OnAfterExecute(IRequestContext requestContext, object response) {
// Called just after any Action is executed, you can modify the response returned here as well
}
public override object HandleException(IRequestContext requestContext, TRequest request, Exception ex) {
// Called whenever an exception is thrown in your Services Action
}
}
Also for more fine-grained Error Handling options check out the Error Handling wiki page.
My solution was to add an additional layer where I can handle Logic per entity:
Base Logic Sample:
public interface IEntity
{
long Id { get; set; }
}
public interface IReadOnlyLogic<Entity> where Entity : class, IEntity
{
List<Entity> GetAll();
Entity GetById(long Id);
}
public abstract class ReadOnlyLogic<Entity> : IReadOnlyLogic<Entity> where Entity : class, IEntity, new()
{
public IDbConnection Db { get; set; }
#region HOOKS
protected SqlExpression<Entity> OnGetList(SqlExpression<Entity> query) { return query; }
protected SqlExpression<Entity> OnGetSingle(SqlExpression<Entity> query) { return OnGetList(query); }
#endregion
public List<Entity> GetAll()
{
var query = OnGetList(Db.From<Entity>());
return Db.Select(query);
}
public Entity GetById(long id)
{
var query = OnGetSingle(Db.From<Entity>())
.Where(e => e.Id == id);
var entity = Db.Single(query);
return entity;
}
}
Then we can use hooks like:
public interface IHello : IReadOnlyLogic<Hello> { }
public class HelloLogic : ReadOnlyLogic<Hello>, IHello
{
protected override SqlExpression<Hello> OnGetList(SqlExpression<Hello> query)
{
return query.Where(h => h.Name == "Something");
}
}
Finally our service only calls our logic:
public class MyServices : Service
{
IHello helloLogic;
public object Get()
{
return helloLogic.GetAll();
}
}