Is it possible to use a concrete implementation, and Refit, to implement the same service in a Blazor Web App?

58 views Asked by At

I'm building a Blazor Web App. I have identified that some pages and components need access to some external data, and I have defined an interface that allows them to access these data.

public interface IHelloWorld
{
  Task<string> GetHelloWorldAsync();
}

In the Blazor Server part of the web app, I want to use a concrete implementation of IHelloWorld which fetches data from the appropriate source.

public class HelloWorld
{
  public Task<string> GetHelloWorldAsync()
  {
    return Task.FromResult("Hello World!");
  }
}

I also want to be able to retrieve these data from the WASM part of the web app, and rather than exposing database servers directly to the internet I want to send requests via a controller in the Blazor Server part. I've written a controller...

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class HelloWorldController : ApiController
{
  private IHelloWorld _helloWorld;

  public HelloWorldController(IHelloWorld helloWorld)
  {
    _helloWorld=helloWorld;
  }
  [HttpGet("HelloWorld")]
  public Task<IActionResult> GetHelloWorld()
  {
    return Ok(await _helloWorld.GetHelloWorldAsync());
  }
}

... and a client, using Refit...

public interface IHelloWorldClient
{
  [Get("/api/HelloWorld/HelloWorld"])
  Task<string> GetHelloWorld();
}

In my Razor component, I can inject an IHelloWorld and use this with server side rendering just fine.

@inject IHelloWorld HelloWorld

@if(!string.IsNullOrEmpty(_greeting)){
  <p>@_greeting</p>
}

@code{
 private string _greeting;
 protected override async Task OnParametersSetAsync()
 {
    var helloWorld = HelloWorld.GetHelloWorldAsync();
 }
}

However, this component cannot run client side because there is no suitable concrete implementation of IHelloWorld. I could inject IHelloWorldClient instead, but then the server side implementation gets a bit ropey because the component ends up calling controller methods against the Blazor Server instance, and I'm sure I've seen Microsoft say that's bad but I don't recall where...

I'd really like for my IHelloWorld to be the single interface that works server side and client side. Server side, I can inject my concrete implementation, and client side I'd like to inject a Refit implementation. I tried being a bit hacky and had my Refit interface inherit from the IHelloWorld and marked each method as new with the appropriate attributes to make Refit work...

public interface IHelloWorldClient : IHelloWorld
{
  [Get("/api/HelloWorld/HelloWorld"])
  new Task<string> GetHelloWorld();
}

... and then tried to use this in the DI container...

builder.Services.AddScoped<IHelloWorld,IHelloWorldClient>();

... but for obvious reasons this fails at runtime as IHelloWorldClient can't be instantiated. When I use Refit, a proxy is automatically generated which implements this interface but I've no idea how to instantiate this myself.

Is it possible to use a very similar approach to get IHelloWorld to work server side and client side, or do I need to write a client side class that implements IHelloWorld and then calls to an injected IHelloWorldClient instead? I'm trying to avoid this as it's a lot of repetition.

0

There are 0 answers