Skip to content

Commit

Permalink
feat: #22 Blazor Server support (#37)
Browse files Browse the repository at this point in the history
* create empty Blazor Server app

* add dependencies

* WIP Blazor Server config

* delete Weather example

* rename Counter to Sample

* created sample page

* WIP RaygunErrorBoundary for Blazor.Server

* Capture errors using error boundary

* update README

* format

* improve README and rename IRaygunUserManager to IRaygunUserProvider as requested
  • Loading branch information
miquelbeltran authored Aug 19, 2024
1 parent 3fb3d07 commit ac2c2d1
Show file tree
Hide file tree
Showing 30 changed files with 810 additions and 47 deletions.
133 changes: 116 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,32 +54,41 @@ Inject the `RaygunBlazorClient` in your code:

And call to `raygunClient.InitializeAsync()` at least once.

- [ ] TODO: Add more info. See `src/Raygun.Blazor/RaygunBlazorClient.cs` for more information.
This method should be called as early as possible in the course of using the app. The best place to do it is inside the
`OnAfterRenderAsync` method of the main layout page. However it will also be called automatically before sending any
exceptions, just in case.

### Recording an error

Call to `raygunClient.RecordExceptionAsync(...)`
To send an exception to Raygun call to `raygunClient.RecordExceptionAsync(...)`

- [ ] TODO: Add more info. See `src/Raygun.Blazor/RaygunBlazorClient.cs` for more information.
This method accepts the following arguments:

### Recording a breadcrumb
- `ex`: The `Exception` to send back to Raygun.
- `userDetails`: Optional. Attach user details to exception, takes priority over `IRaygunUserProvider`.
- `tags`: Optional. User-specified tags that should be applied to the error.
- `userCustomData`: Optional. Any custom data that you you like sent with the report to assist with troubleshooting.
- `cancellationToken`: Optional. A `CancellationToken` to allow you to cancel the current request, if necessary.

Call to `raygunClient.RecordBreadcrumb(...);`
### Recording a breadcrumb

- [ ] TODO: Add more info. See `src/Raygun.Blazor/RaygunBlazorClient.cs` for more information.
Records a Breadcrumb to help you track what was going on in your application before an error occurred.

### `RaygunErrorBoundary`
Call to `raygunClient.RecordBreadcrumb(...);`

- [ ] TODO: Document when to use the `RaygunErrorBoundary`.
This method accepts the following arguments:

Currently used in `src/Raygun.Samples.Blazor.WebAssembly/App.razor`
- `message`: The message you want to record for this Breadcrumb.
- `type`: The `BreadcrumbType` for the message. Defaults to `BreadcrumbType.Manual`.
- `category`: A custom value used to arbitrarily group this Breadcrumb.
- `customData`: Any custom data you want to record about application state when the Breadcrumb was recorded.

### Attaching user details

Raygun for Blazor provides two ways to attach user details to error reports:

1. Provide `UserDetails` in the `RecordExceptionAsync` method call.
2. Implement a `IRaygunUserManager`.
2. Implement a `IRaygunUserProvider`.

#### User details class

Expand All @@ -106,14 +115,14 @@ var userDetails = new UserDetails() { Email = "[email protected]", FullName = "Te
await RaygunClient.RecordExceptionAsync(ex, userDetails);
```

#### Implementing `IRaygunUserManager`
#### Implementing `IRaygunUserProvider`

Providing an instance of `IRaygunUserManager` to the Raygun Blazor client allows you to attach user details also to errors reported automatically, for example, captured unhandled exceptions or exceptions from the JavaScript layer.
Providing an instance of `IRaygunUserProvider` to the Raygun Blazor client allows you to attach user details also to errors reported automatically, for example, captured unhandled exceptions or exceptions from the JavaScript layer.

Implement an `IRaygunUserManager`, for example:
Implement an `IRaygunUserProvider`, for example:

```cs
public class MyUserManager : IRaygunUserManager
public class MyUserProvider : IRaygunUserProvider
{
public Task<UserDetails?> GetCurrentUser()
{
Expand All @@ -125,10 +134,10 @@ public class MyUserManager : IRaygunUserManager
And inject it into the Raygun Blazor client:

```cs
builder.Services.AddSingleton<IRaygunUserManager, MyUserManager>();
builder.Services.AddSingleton<IRaygunUserProvider, MyUserProvider>();
```

For a complete example on how to implement a `IRaygunUserManager` with the `AuthenticationStateProvider` check the example project file `src/Raygun.Samples.Blazor.WebAssembly/Program.cs`.
For a complete example on how to implement a `IRaygunUserProvider` with the `AuthenticationStateProvider` check the example project file `src/Raygun.Samples.Blazor.WebAssembly/Program.cs`.

### Internal logger

Expand All @@ -150,7 +159,13 @@ For all configuration values, check the `RaygunLogLevel` enum under `src/Raygun.

---

## Example Project
## Blazor WebAssembly

### Setup

- [ ] TODO: setup WebAssembly instructions

### Example

Example project is located in `src/Raygun.Samples.Blazor.WebAssembly`

Expand All @@ -171,6 +186,90 @@ To run the example:

A browser window to `http://localhost:5010/` should automatically open.

## Blazor Server

### Installation

- [ ] TODO: NuGet install instructions

### Setup

Add a scoped `RaygunBlazorClient` by calling to `UseRaygunBlazor()` with your `WebApplication` builder.

```cs
var builder = WebApplication.CreateBuilder(args);

...

builder.UseRaygunBlazor();
```

### Accessing `RaygunBlazorClient`

You can access the `RaygunBlazorClient` using `@inject` in your code:

```cs
@inject RaygunBlazorClient RaygunClient

...

RaygunClient.RecordExceptionAsync(...)
```

### Capturing unhandled exceptions

Use `RaygunErrorBoundary` to wrap compoments and capture unhandled exceptions automatically.

Note: You have to set `@rendermode="InteractiveServer"` in your `HeadOutlet` and `Routes` component to enable error capturing, as explained in [Handle errors in ASP.NET Core Blazor apps](https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/handle-errors?view=aspnetcore-8.0#error-boundaries)

For example, in your `MainLayout.razor`:

```cs
@using Raygun.Blazor.Server.Controls

...

<article class="content px-4">
<RaygunErrorBoundary>
@Body
</RaygunErrorBoundary>
</article>
```

You can set `ShowExceptionsUI="true` to display a custom error message:

```cs
<RaygunErrorBoundary ShowExceptionUI="true">
<ChildContent>
@Body
</ChildContent>
<ErrorContent>
<p class="errorUI">👾 Error captured by Raygun!</p>
</ErrorContent>
</RaygunErrorBoundary>
```

### Example

Example project is located in `src/Raygun.Samples.Blazor.Server`

To run the example:

1. Install `dotnet-sdk` minimum version supported in `8.0.300`.
2. Add the `ApiKey` property to in `src/Raygun.Samples.Blazor.Server/appsettings.Development.json`

```
{
"Raygun": {
"ApiKey": "YOUR_API_KEY"
}
}
```

3. Run `dotnet watch` from the example folder.

A browser window to `http://localhost:5010/` should automatically open.

---

## Publishing
Expand Down
10 changes: 0 additions & 10 deletions src/Raygun.Blazor.Server/Class1.cs

This file was deleted.

101 changes: 101 additions & 0 deletions src/Raygun.Blazor.Server/Controls/RaygunErrorBoundary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Options;

namespace Raygun.Blazor.Server.Controls
{

/// <summary>
/// An extension of the Blazor <see cref="ErrorBoundary" /> control that automatically sends exceptions to Raygun.
/// </summary>
public class RaygunErrorBoundary : ErrorBoundary
{

#region Internal Parameters

/// <summary>
///
/// </summary>
[Inject]
internal RaygunBlazorClient RaygunClient { get; set; }

/// <summary>
///
/// </summary>
[Inject]
internal IOptions<RaygunSettings> RaygunSettings { get; set; }

#endregion

#region Public Parameters

/// <summary>
///
/// </summary>
[Parameter]
public bool ShowExceptionUI { get; set; }

#endregion

#region Internal Methods

/// <summary>
/// When an error occurs, send it to Raygun.
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
protected override async Task OnErrorAsync(Exception exception)
{
Console.WriteLine("OnErrorAsync");
if (!RaygunSettings.Value.CatchUnhandledExceptions) return;

await RaygunClient.RecordExceptionAsync(exception, null, ["UnhandledException", "Blazor", ".NET"]);
}

/// <inheritdoc />
/// <remarks>
/// We are rendering differently than the ErrorBoundary base control because Raygun's ethos is to first not
/// mess with anything about the app. So if the developer wants to display UI, they have to specifically opt-in.
/// </remarks>
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (CurrentException is not null && ShowExceptionUI)
{
if (ErrorContent is not null)
{
builder.AddContent(1, ErrorContent(CurrentException));
}
else
{
// RWM: We may need to consider invoking JavaScript to set the "blazor-error-ui" visible instead.
// The code sets the style to display-block;


// The default error UI doesn't include any content, because:
// [1] We don't know whether or not you'd be happy to show the stack trace. It depends both on
// whether DetailedErrors is enabled and whether you're in production, because even on WebAssembly
// you likely don't want to put technical data like that in the UI for end users. A reasonable way
// to toggle this is via something like "#if DEBUG" but that can only be done in user code.
// [2] We can't have any other human-readable content by default, because it would need to be valid
// for all languages.
// Instead, the default project template provides locale-specific default content via CSS. This provides
// a quick form of customization even without having to subclass this component.
builder.OpenElement(2, "div");
builder.AddAttribute(3, "class", "blazor-error-boundary");
builder.CloseElement();
}
}
else
{
builder.AddContent(0, ChildContent);
}
}

#endregion

}

}
30 changes: 29 additions & 1 deletion src/Raygun.Blazor.Server/Extensions/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
namespace Raygun.Blazor.Server.Extensions
using System;
using KristofferStrube.Blazor.Window;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace Raygun.Blazor.Server.Extensions
{

/// <summary>
///
/// </summary>
public static class HostBuilderExtensions
{
/// <summary>
///
/// </summary>
/// <param name="builder"></param>
/// <param name="configSectionName"></param>
public static void UseRaygunBlazor(this WebApplicationBuilder builder, string configSectionName = "Raygun")
{
builder.Services.Configure<RaygunSettings>(builder.Configuration.GetSection(configSectionName));
builder.Services.AddScoped<RaygunBrowserInterop>();
builder.Services.AddScoped<IWindowService, WindowService>();

builder.Services.AddHttpClient("Raygun")
.ConfigureHttpClient((sp, client) =>
{
var raygunSettings = sp.GetRequiredService<IOptions<RaygunSettings>>().Value;
client.BaseAddress = new Uri(raygunSettings.Endpoint);
client.DefaultRequestHeaders.Add("X-ApiKey", raygunSettings.ApiKey);
// TODO: RWM: Set user agent
});

builder.Services.AddScoped<RaygunBlazorClient>();
}
}

}
8 changes: 7 additions & 1 deletion src/Raygun.Blazor.Server/Raygun.Blazor.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

<ItemGroup>
<!-- Package Readme File at root of repo -->
<None Include="../../README.md" Pack="true" PackagePath=""/>
<None Include="../../README.md" Pack="true" PackagePath="" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Raygun.Blazor\Raygun.Blazor.csproj" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Raygun.Blazor.Extensions;
using Raygun.Blazor.Logging;


namespace Raygun.Blazor.WebAssembly.Extensions
Expand Down
6 changes: 6 additions & 0 deletions src/Raygun.Blazor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Raygun.Blazor.Server", "Ray
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Raygun.Samples.Blazor.WebAssembly", "Raygun.Samples.Blazor.WebAssembly\Raygun.Samples.Blazor.WebAssembly.csproj", "{CDBE774B-4AEA-4277-9044-A9718C03CEF6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raygun.Samples.Blazor.Server", "Raygun.Samples.Blazor.Server\Raygun.Samples.Blazor.Server.csproj", "{333DF481-7A4F-4C9A-981F-63C954D0414D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -60,6 +62,10 @@ Global
{CDBE774B-4AEA-4277-9044-A9718C03CEF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CDBE774B-4AEA-4277-9044-A9718C03CEF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CDBE774B-4AEA-4277-9044-A9718C03CEF6}.Release|Any CPU.Build.0 = Release|Any CPU
{333DF481-7A4F-4C9A-981F-63C954D0414D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{333DF481-7A4F-4C9A-981F-63C954D0414D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{333DF481-7A4F-4C9A-981F-63C954D0414D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{333DF481-7A4F-4C9A-981F-63C954D0414D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Raygun.Blazor.Interfaces
/// <summary>
/// Defines the contract for a user manager for Raygun error reports.
/// </summary>
public interface IRaygunUserManager
public interface IRaygunUserProvider
{

/// <summary>
Expand Down
Loading

0 comments on commit ac2c2d1

Please sign in to comment.