gRPC Channel throws null reference exception in Blazor client - c#

I've a gRPC service written in .NET 6 that is running fine on localhost. I'm trying to consume it in a Blazor client for .NET 6. but, I'm getting System.NullReferenceException when creating that channel for my gRPC service.
SERVICE HELPER
public class ServiceHelper : IServiceHelper
{
public Server.ServerClient? ServerClient { get; set; }
public ServiceHelper()
{
try
{
GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:7027");
ServerClient = new Server.ServerClient(channel);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
Program.cs for Client
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddAntDesign();
//var serviceAddress = builder.Configuration["ServiceAddress"];
builder.Services.AddSingleton<IServiceHelper, ServiceHelper>();
await builder.Build().RunAsync();
Using the gRPC Client
#inject IServiceHelper _serviceHelper
<div class="page elementFillSpaceY">
</div>
#code
{
public ServerStatus? ServerStatus { get; set; }
protected override async Task OnInitializedAsync()
{
await SetUpData();
}
private async Task SetUpData()
{
var client = _serviceHelper.ServerClient;
if (client != null)
{
var result = await client.GetServerStatusAsync(new Empty());
ServerStatus = result;
Debug.WriteLine(ServerStatus.Status);
}
}
}
Program.cs for gRPC Service
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddGrpc();
const string corsPolicy = "_corsPolicy";
/*builder.Services.AddCors(options =>
{
options.AddPolicy(name: corsPolicy,
policy =>
{
/*policy.WithOrigins("https://localhost:7075",
"http://localhost:5025")
policy.AllowAnyOrigin()
.WithExposedHeaders("grpc-status", "grpc-message")
.AllowAnyHeader()
.AllowAnyMethod();
});
});*/
var app = builder.Build();
//app.UseCors(corsPolicy);
//app.UseRouting();
//app.UseGrpcWeb();
// Configure the HTTP request pipeline.
app.MapGrpcService<GreeterService>();
app.MapGrpcService<ServerService>();
app.MapGet("/",
() =>
"Communication with gRPC endpoints must be made through a gRPC client.");
app.Run();
I've tried different ways of consuming gRPC service in client e.g. Dependency Injection for Channel and on gRPC side look at the commented code that i have tried but still getting same error. It just throws the null reference exception without any other detail.
Stack trace of exception
'at
Grpc.Net.Client.Balancer.Internal.BalancerHttpHandler..ctor(HttpMessageHandler
innerHandler, HttpHandlerType httpHandlerType, ConnectionManager
manager)\n at
Grpc.Net.Client.GrpcChannel.CreateInternalHttpInvoker(HttpMessageHandler
handler)\n at Grpc.Net.Client.GrpcChannel..ctor(Uri address,
GrpcChannelOptions channelOptions)\n at
Grpc.Net.Client.GrpcChannel.ForAddress(Uri address, GrpcChannelOptions
channelOptions)\n at Grpc.Net.Client.GrpcChannel.ForAddress(String
address, GrpcChannelOptions channelOptions)\n at
Grpc.Net.Client.GrpcChannel.ForAddress(String address)\n at
BusinessBlazor.Helpers.ServiceHelper..ctor() in ....'

I am not exactly sure why the exception is thrown. But, with a pretty high probability, it may be thrown because the GrpcChannel.ForAddress can not construct a valid HttpClient for the communication. Possible steps to debug and fix this issue
Ensure that your server is actually up and accessible via the URL https://localhost:7027
Try to construct and pass the HttpClient explicitly. See example here. Also, note that in the client service you registered the HttpClient as Scoped, so you won't be able to fetch it from DI container in the Singleton service.
Try to add the gRPC client via builder.Services.AddGrpcClient and get it from the DI. Example here
I really hope that it will help to solve your problem. Good luck!

Related

Call API from AccountClaimsPrincipalFactory in Blazor WASM

I am using Blazor WASM with AzureB2C to call an API hosted in Azure Functions. I would like to call my API on a successful login to add/update user info into a database. I have been following this guide. When trying to inject my typed httpclient into the AccountClaimsPrincipalFactory I am met with a runtime error:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: ValueFactory attempted to access the Value property of this instance.
System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
This shows in the browser, but the app compiles and runs just fine. The codes works great if I don't inject my PlatformServiceClient, but I need to make the API call to record the user. The following files are involved. I adjusted some things to simplify. This seems like the appropriate approach, but I have not seen examples where an api call was made in the claims factory.
CustomAccountFactory.cs
public class CustomAccountFactory: AccountClaimsPrincipalFactory<CustomUserAccount>
{
public IPlatformServiceClient client { get; set; }
public CustomAccountFactory(NavigationManager navigationManager,
IPlatformServiceClient platformServiceClient,
IAccessTokenProviderAccessor accessor) : base(accessor)
{
client = platformServiceClient;
}
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
CustomUserAccount account, RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);
if (initialUser.Identity.IsAuthenticated)
{
//call the API here
await client.RegisterUserAsync();
}
return initialUser;
}
}
Program.cs excerpt
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient<IPlatformServiceClient, PlatformServiceClient>(
client => client.BaseAddress = new Uri(builder.Configuration["PlatformServiceUrl"]))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
options.ProviderOptions.DefaultAccessTokenScopes.Add("access_as_user");
options.ProviderOptions.LoginMode = "redirect";
options.UserOptions.RoleClaim = "roles";
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, CustomAccountFactory>();
CustomAuthorizationMessageHandler.cs
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "http://localhost:7071" },
scopes: new[] { "access_as_user" });
}
}
I solved this by creating a named instance of the client and passing an IHttpClientFactory into the CustomAccountFactory.
builder.Services.AddHttpClient<PlatformServiceClient>("PlatformServiceClient",
client => client.BaseAddress = new Uri(builder.Configuration["PlatformServiceUrl"]))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
There I can create a client, but I have to setup my urls manually vs using the typed client where I have this work already done.
var client = factory.CreateClient("PlatformServiceClient");
var response = await client.GetAsync("/user/me");
I also registered the new client prior to calling AddMsalAuthenication
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("PlatformServiceClient"));
I did all of this following the code found here by Coding Flamingo. It is all working as expected.

The HTTP client does not redirect in the integration tests

I have setup an integration test:
public class IntegrationTests
{
private readonly TestServer _server;
private readonly HttpClient _client;
public IntegrationTests()
{
_server = new TestServer(WebHost.CreateDefaultBuilder().UseEnvironment("Development").UseStartup<Startup>())
{
PreserveExecutionContext = true,
};
_client = _server.CreateClient();
}
[Test]
public async Task RunARoute()
{
var response = await _client.GetAsync("/foo");
Check.That(response.IsSuccessStatusCode).IsTrue();
}
}
The startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddApplicationPart(typeof(HomeController).Assembly)
.AddControllersAsServices()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services
.ConfigureAll(Configuration) // Add the configuration sections
.AddAllServices() // DI
// Other:
.AddAutoMapperProfiles(AutoMapperConfiguration.LoadConfig)
.AddCacheHelper(e => {})
.AddSession(opt => opt.Cookie.IsEssential = true);
}
public void Configure(IApplicationBuilder app)
{
app.UseHttpsRedirection()
.UseStaticFiles()
.UseRouting()
.UseCookiePolicy()
.UseSession()
.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
In the test method, the route that I call redirects to another route: return RedirectToAction(nameof(Bar)). I'd like to test that the page is correctly returned by the Bar method, but unfortunately, the HttpClient does not redirect the call: my test fails with a code 302.
I've read on the Internet that this issue usually occurs when there is an attempt of a redirection from an HTTPS route to an HTTP one, but AFAIK, that's not the case here, since the test server creates a client with the base URL http://localhost/, and the redirection URL is relative (so no protocol specified).
How can I ensure that the client redirects the calls?
That is by design. If you check the TestServer source code
public HttpMessageHandler CreateHandler()
{
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
return new ClientHandler(pathBase, Application) { AllowSynchronousIO = AllowSynchronousIO, PreserveExecutionContext = PreserveExecutionContext };
}
public HttpClient CreateClient()
{
return new HttpClient(CreateHandler()) { BaseAddress = BaseAddress };
}
you'll see it does not enable auto redirect feature which is actually a part of the HttpClientHandler normally used by default HttpClient.
TestServer however, uses a custom handler that does not auto redirect when creating the HttpClient. You would need to have access to the handler, which you can't since the test server creates the client internally.
Therefore the code as descried is behaving as expected.
The HTTP response status code 302 Found is a common way of performing a redirection.
Check the response header to assert that the redirect location header is to the desired URL in order to assert the expected behavior.
You can also consider manually calling the redirected URL to verify that it will return the HTTP response status code 200 OK
[Test]
public async Task RunARoute_Should_Redirect() {
_server.PreserveExecutionContext = true;
var client = _server.CreateClient();
var response = await _client.GetAsync("/foo");
Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.Found); //302 Found
var redirectUrl = response.Headers.Location;
//assert expected redirect URL
//...
response = await _client.GetAsync(redirectUrl);
Check.That(response.IsSuccessStatusCode).IsTrue(); //200 OK
}

MassTransit RabbitMQ AspNetCore not starting the bus and registering receive endpoints

I've been battling with this for hours now but I can't seem to get it to work despite seemingly copying the documentation.
I'm using the new MassTransit AspNetCore package to register my bus and a consumer but it doesn't seem to be starting the bus! If I add a break point by the code the registers the receive endpoint it never gets hit (but the AddBus part does) and the exchange/queue doesn't get created.
It's worth noting that this is just a Web API that runs in IIS and that there are also normal rest endpoints too (I don't know if that would matter or not).
My code is as follows:
services.AddMassTransit(x =>
{
x.AddConsumer<SelectAllAccessibleConsumer>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri(rabbitMqMessageBus.Host), rabbitMqMessageBus.Username,
hostConfigurator =>
{
hostConfigurator.Username(rabbitMqMessageBus.Username);
hostConfigurator.Password(rabbitMqMessageBus.Password);
if (rabbitMqMessageBus.UseSsl)
{
hostConfigurator.UseSsl(s => { s.Protocol = SslProtocols.Tls12; });
}
});
cfg.ReceiveEndpoint(host, "queue-name", ep =>
{
ep.PrefetchCount = 16;
ep.UseMessageRetry(r => r.Interval(2, 100));
ep.ConfigureConsumer<SelectAllAccessibleConsumer>(provider);
});
}));
});
I can't help but feel like I'm missing something immensely obvious but for the life of me I can't figure it out.
You need to actually start the bus, which in your scenario should be done using a hosted service. You can see the sample here:
https://github.com/MassTransit/Sample-ConsoleService/blob/master/SampleService/MassTransitConsoleHostedService.cs
public class MassTransitConsoleHostedService :
IHostedService
{
readonly IBusControl _bus;
public MassTransitConsoleHostedService(IBusControl bus, ILoggerFactory loggerFactory)
{
_bus = bus;
if (loggerFactory != null && MassTransit.Logging.Logger.Current.GetType() == typeof(TraceLogger))
MassTransit.ExtensionsLoggingIntegration.ExtensionsLogger.Use(loggerFactory);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await _bus.StartAsync(cancellationToken).ConfigureAwait(false);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _bus.StopAsync(cancellationToken);
}
}
And then, add to your configuration:
services.AddSingleton<IHostedService, MassTransitConsoleHostedService>();
Actually the IServiceHost registration is included in the library though it is not mentioned in the documentation.
Need to use the extension:
services.AddMassTransitHostedService()

Nservicebus unity Endpoint failed to start

Using .NET 4.5.2, Visual studio 2017, C# 7.1, Unity, NServiceBus 6.
I receive the following error:
My application is a console app, here's some of the Program.cs code:
private static async Task ConfigureUnity()
{
IUnityContainer container = new UnityContainer();
var endpointConfiguration = new EndpointConfiguration("NSB.ChannelAdvisorService");
var transport = endpointConfiguration.UseTransport<LearningTransport>();
endpointConfiguration.AssemblyScanner().ExcludeAssemblies("netstandard");
endpointConfiguration.UseContainer<UnityBuilder>(
customizations =>
{
customizations.UseExistingContainer(container);
});
var endpointInstance = Endpoint.Start(endpointConfiguration).GetAwaiter().GetResult();
//register
container.RegisterType(typeof(IGenericHttpRequestRepository<>), typeof(GenericHttpRequestRepository<>), new TransientLifetimeManager());
container.RegisterType<IOrderRepository, OrderRepository>();
container.RegisterType<IShipmentRepository, ShipmentRepository>();
container.RegisterType<IOrderProcessService, OrderProcessService>();
container.RegisterType<IShipmentService, ShipmentService>();
container.RegisterInstance(endpointConfiguration);
//resolve
var orderProcessService = container.Resolve<IOrderProcessService>();
var shipmentService = container.Resolve<IShipmentService>();
.....
As you can see I'm using Unity and NServiceBus, this is to register DI and also use it withing NServicebus so i can DI it into my service to send a command.
The service trys to DI "IEndpointInstance"
public class OrderProcessService : IOrderProcessService
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private readonly IEndpointInstance _endpoint;
public OrderProcessService(IEndpointInstance endpoint)
{
_endpoint = endpoint;
}
public async Task PostNewOrderBatch()
{
var list = _orderRepository.GetBatchedOrders();
foreach(var item in list)// parallel this?
{
await _endpoint.Send(item.ToObject<ProcessBatchOrdersCommand>()).ConfigureAwait(false);
_orderRepository.DeleteFile(item.Property("FilePath").Value.ToString());
}
}
}
I get the feeling it could be an issue about the order of things, I don't think I've missed anything out as far as i can tell in some examples?
In NServiceBus v6 and later the endpoint instance is no longer automatically registered in the container. You need to register the endpoint instance returned from Endpoint.Start(configuration) on the existing container.
See https://docs.particular.net/nservicebus/dependency-injection/#using-an-existing-instance-endpoint-resolution

Signalr connects but doesn't push a message

I've been working on implementing signalr as part of a wcf service to talk to a .net client. Apart form a connection message all communication is one way passing a dynamic payload to the client side.
I've managed to set it up so that the client will connect to the service and pass a connection message but I can't get the pushing of a message from the service to the client.
Sorry if I've missed this answered else where but I've been unable to find a reason for this failing as it seems to follow the "how to's"
Any help would be much appreciated and thank you in advance
Server side:
WCF external call
public class MessageService : IMessageService
{
public string PushAlerts()
{
var payLoad = new PayLoad
{
MethodName = "alerts"
};
IHubContext connectionHub = GlobalHost.ConnectionManager.GetHubContext<PushConnection>();
connectionHub.Clients.All.Notify(payLoad);
}
}
My Hub
[HubName("PushHub")]
public class PushHub : Hub
{
public override Task OnConnected()
{
var connectionMessage = Context.QueryString["CONNECTION MESSAGE"];
if (connectionMessage != null)
{
Debug.WriteLine("connectionMessage");
}
return base.OnConnected();
}
}
ClientSide:
var querystringData = new Dictionary<string, string>{};
querystringData.Add("CONNECTION MESSAGE", "foo Connection");
var hubConnection = new HubConnection("http://localhost:60479/", querystringData); //Running local till working
hubConnection.TraceLevel = TraceLevels.All;
hubConnection.TraceWriter = Console.Out;
IHubProxy clientHubProxy = hubConnection.CreateHubProxy("PushHub");
clientHubProxy.On("Notify", payLoad =>
SynchronizationContext.Current.Post(delegate
{
ResponseMethod(payLoad);
}, null)
);
await hubConnection.Start();
I've missed out payload but that only holds a string value at present. I've also setup a pipemodule for logging perposes.
Thanks Again
Ok so I resolved this problem in two ways firstly I moved the call to the client inside the hub its self, which I then called from a method in my wcf service.
[HubName("PushHub")]
public class PushHub : Hub
{
IHubContext connectionHub = GlobalHost.ConnectionManager.GetHubContext<PushConnection>();
public void Send(Payload payload)
{
connectionHub.Clients.All.Notify(payLoad);
}
}
Secondly the client code for the method was all wrong. In the end this worked:
clientHubProxy.On("Notify", (payLoad) => { dostuff };
Took a lot of fiddling but hope my answer helps others.

Categories