asp.net core rest api and Uno-platform wasm - uno-platform

From uno platform WASM client I am trying to call a asp.net core rest demo:
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
When the wasm client calls the rest I get TypeError: Failed to fetch but this works just fine UWP and console apps.
Client is using
public async Task<string> RefreshDataAsync(string x)
{
var _client = new HttpClient();
var response = await _client.GetStringAsync(#"http://localhost:58658/weatherforecast");
return response;
}
Does WASM uno platform support rest api calls?

Turns out the rest api needed
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
);

Consumption of web-services (assuming http/json) in the Uno Platform works just the same as any .NET application. Use HttpClient
For WebAssembly you'll need to create a WasmHttpHandler and then pass it in as the innerHandler of HttpClient.
#if __WASM__
var innerHandler = new Uno.UI.Wasm.WasmHttpHandler();
#else
var innerHandler = new HttpClientHandler();
#endif
_httpClient = new HttpClient(innerHandler);
See https://github.com/unoplatform/uado for an example using HttpClient.

Related

Core app logging requests but not traces to app insights

I noticed that my .net core app is logging requests, but not traces to my azure app insights.
I'm setting up app insights in my Startup.cs
services.AddApplicationInsightsTelemetry(options =>
{
options.InstrumentationKey = Configuration["ApplicationInsightsInstrumentationKey"];
});
I'm injecting ILogger in my controller like such
private readonly ILogger<HealthCheckController> _logger;
public HealthCheckController(ILogger<HealthCheckController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult HealthCheck()
{
// Custom EventId for logging
var eventId = EventDefinition.TestMicroservice.HealthCheckController.HealthCheck;
_logger.LogInformation(eventId, "Test Microservice health check successful.");
return Ok("Test Microservice is running!");
}
Am I missing something? I've hit my controller multiple times and I'm seeing requests getting logged, but my custom log messages are nowhere to be found under traces.
So what I found was that I was able to inject TelemetryClient despite wanting to use ILogger. While I kept the above code in, I had to add the .ConfigureLogging
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext =>
new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");
return new WebHostBuilder()
.UseKestrel()
.ConfigureServices(
services => services
.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((context, config) =>
{
var configurationPackage = FabricRuntime.GetActivationContext()?.GetConfigurationPackageObject("Config");
// Add Key Vault
var kv = configurationPackage.Settings.Sections[KeyVaultOptions.Position].Parameters;
var credential = new ClientSecretCredential(kv["TenantId"].Value, kv["ClientId"].Value, kv["ClientSecret"].Value);
var client = new SecretClient(new Uri(kv["Url"].Value), credential);
config.AddAzureKeyVault(client, new AzureKeyVaultConfigurationOptions());
config.Build();
})
.ConfigureLogging((context, logging) =>
{
logging.AddApplicationInsights(
context.Configuration["ApplicationInsightsInstrumentationKey"]);
logging.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
})
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseUrls(url)
.Build();
}))
};
By default, only Warning or higher is collected by ApplicationInsights. You can change it following the doc here: https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core#how-do-i-customize-ilogger-logs-collection

Unit test for web API application with dotnet 6

I have a web API application with JWT authentication, I want to write test for it, I don't know use XUnit or call APIs with HTTP client!
XUnit
[Fact]
public async Task Authenticate_WithValidUsernamePassword_ReturnsToken()
{
//...
}
Http client
[TestMethod]
public async Task Authenticate_WithValidUsernamePassword_ReturnsToken()
{
HttpClient _client =...
var httpResponse = await _client.GetAsync("api/v1/admin/Authenticate?....");
}
Try xUnit like this:
[Fact]
public async Task Authenticate_WithValidUsernamePassword_ReturnsToken()
{
using var client = new HttpClient();
var content = await client.GetStringAsync("/api/v1/admin/Authenticate?....");
bool result = false;
if (content == "foo authentication ok") {
result = true;
}
Assert.True(result, $"foo authentication failed Result={result}");
}

SignalR gives 404 when using WebSockets or ServerSentEvents for some users but not for others

SignalR gives me 404 when trying to connect for some users. URLs are the same except for access_token.
It is stable reproducible per user (I mean that some users are stable OK, some users are stable 404).
access_token parsed jwt diff (left is OK user, right gets 404):
I did a trace level of logs and have next:
For the OK user:
For the user that gets 404:
Note: URLs under black squares are the same.
Front End is Angular 9 with package "#microsoft/signalr": "^3.1.8", and here's the code that builds the connection:
private buildHub(): HubConnection {
console.log(this.authService.accessToken);
let builder = new HubConnectionBuilder()
.withAutomaticReconnect()
.configureLogging(LogLevel.Information)
.withUrl('ws/notificationHub', {
accessTokenFactory: () => this.authService.accessToken
});
if (this.debugMode) {
builder = builder.configureLogging(LogLevel.Trace);
}
return builder.build();
}
Backend is using next code in Startup for configuring signalR hub:
In public void ConfigureServices(IServiceCollection services):
services.AddSignalR()
.AddJsonProtocol(options =>
{
options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
});
In public void Configure(IApplicationBuilder app, IHostingEnvironment env):
app.UseSignalR(route =>
{
route.MapHub<NotificationHub>("/ws/notificationHub");
});
Also we use custom authentication, so we have Authorize attribute for the Hub class:
[Authorize]
public class NotificationHub: Hub<INotificationHubClient>
and this code in public void ConfigureServices(IServiceCollection services):
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = identityServerSettings.Url;
options.Audience = identityServerSettings.ApiScopeName;
options.RequireHttpsMetadata = identityServerSettings.RequireHttpsMetadata;
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/ws"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
Unfortunately, I don't have the full access to the environment where it is reproducible, but I can request to see any settings or try to make some changes.
What else can I try to troubleshoot the issue?
UPDATE: negotiate is fine for both users.
I had this issue recently, after the size of my JWT increased. I found that in my case the 404 error was being thrown by IIS because the query string exceeded the limit of 2048. After increasing the query string max length, my issue was resolved.

Cors issue with Blazor webassembly standalone client

I am struggling with some cors issue for DELETE and POST.
GET works fine.
Setup is: .Net core API in Azure API Management and Blazor webassembly standalone client (Azure app service) that calls the API.
Error I get when try to DELETE is.
"has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present"
So I find it difficult to understand what code that is needed here. It is so many example everywhere on CORS for .net core and also the different Blazor setups (server, hosted wasm etc).
I guess I need to handle the preflight request in some way for this to work?
This is what I use right now:
My ServiceTimes.razor that calls the API
#code {
private const string ServiceEndpoint = "https://MyProdAPI.azure-api.net/api/ServiceTimes";
private LWS_ServiceTimes[] servicetimes;
LWS_ServiceTimes servicetimeObj = new LWS_ServiceTimes();
string ids = "0";
bool showAddrow = false;
bool loadFailed;
protected override async Task OnInitializedAsync()
{
ids = "0";
try
{
servicetimes = await Http.GetFromJsonAsync<LWS_ServiceTimes[]>(ServiceEndpoint);
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
// Delete Method
protected async Task DeleteServiceTimes(long ServiceTimesID)
{
showAddrow = false;
ids = ServiceTimesID.ToString();
await Http.DeleteAsync("https://MyprodAPI.azure-api.net/api/ServiceTimes/1"); //Deletes the ID=1
}
Blazor webassembly standalone client Program.cs
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://prodsite.azurewebsites.net") });
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://xxxxxxxx-xxxx-xxxx-xxxx-xxxx/API.Access"); //API.Acess my scope in API
});
builder.Services.AddHttpClient("ServerAPI",
client => client.BaseAddress = new Uri("https://MyprodAPI.azure-api.net"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"));
await builder.Build().RunAsync();
}
API Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<LwsSpiderContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyDatabase")));
services.AddSwaggerGen();
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("https://prodsite.azurewebsites.net");
builder.AllowAnyMethod();
builder.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-custom-header");
builder.AllowCredentials();
});
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
// app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("MyAllowSpecificOrigins");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Connect asp.net core web api with Blazor Web assembly like Angular SPA

I am new to Bazor web assembly (Blazor Client).
I have created a project with Asp.net Core web api with Angular Application.
In order to work with asp.net core web api and angular,
I can use the default functionality like
AddSpaStaticFiles
UseSpa
How can I use Blazor webassembly like the angular?
Or
How can replace the existing Angular SPA with Blazor Client?
Some links provided a solution for Blazor assembly preview.
But the same functionality not found on the latest.
https://csharp.christiannagel.com/2019/08/27/blazorserverandclient/
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
});
Remember that Web Assembly Apps are created to work like another SPA like Angular or React, it means that you create your view presentation or Blazor Web Assembly App in a independent project, then you get the data from some Web Service, for example an Rest API made in .Net Core 3.1, to create the Blazor Web Assembly Project you just go to File -> New -> Project -> Blazor App -> Blazor WebAssembly App, do not choose ASP.NET Core Hosted option, this will attach your project to the .net core backend directly like an MVC Project.
After having your new project created, you just simple need to call your backend end-points with the Built-In Http Client library that comes with .Net Core or you can create your own library using .Net HttpClient and Inject it in your components or pages using Dependency Injection, if you want to create your own library, follow this process:
First Create this HttpObject:
public class HttpResultObject<T>
{
public bool IsSuccessful { get; set; }
public HttpStatusCode HttpResultCode { get; set; }
public T Result { get; set; }
}
Then create your Library Class:
public class MyLibrary : IMyLibrary
{
public string GetApiUri(string server)
{
if (server.Equals("auth"))
return "https://localhost:8080/api/";
return "https://localhost:8080/api/";
}
//Http Get Method Example
public async Task<HttpResultObject<U>> SetAppMethodGetParametersAsync<U>(string server, string method, Dictionary<string, object> parameters, CancellationToken token) where U : class
{
string getParameters = string.Empty;
foreach(var p in parameters)
{
if (p.Value.GetType() == typeof(string))
getParameters = getParameters.Equals(string.Empty) ? "?" + p.Value.ToString() : "&" + p.Value.ToString();
}
var uri = new System.Uri(GetApiUri(server) + method + "/" + getParameters) ;
var response = await CallAppMethodGetAsync(uri, token);
var result = new HttpResultObject<U>()
{
IsSuccessful = response.IsSuccessStatusCode,
HttpResultCode = response.StatusCode,
Result = JsonSerializer.Deserialize<U>(response?.Content?.ReadAsStringAsync()?.Result)
};
return result;
}
private async Task<HttpResponseMessage> CallAppMethodGetAsync(System.Uri uri, CancellationToken token)
{
Console.WriteLine(uri.ToString());
HttpStatusCode responseStatus = HttpStatusCode.BadRequest;
try
{
HttpClient client = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(240000)
};
HttpResponseMessage response = new HttpResponseMessage(responseStatus);
HttpRequestMessage request = new HttpRequestMessage()
{
RequestUri = uri,
Method = HttpMethod.Get
};
var authToken = this.GetLocalStorageItem("authToken");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (authToken != null && authToken.GetType() == typeof(string))
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Convert.ToString(authToken));
token.ThrowIfCancellationRequested();
response = await client.SendAsync(request, token);
responseStatus = response == null ? HttpStatusCode.BadRequest : response.StatusCode;
if (response != null && responseStatus != HttpStatusCode.OK && responseStatus != HttpStatusCode.Accepted)
{
HttpResponseMessage result = new HttpResponseMessage(responseStatus)
{
Content = new StringContent(response.Content?.ReadAsStringAsync()?.Result, Encoding.UTF8, "application/json")
};
return response;
}
return response;
}
catch (WebException webException)
{
}
catch (System.Net.Sockets.SocketException socketException)
{
}
catch (HttpRequestException httpRequestException)
{
}
catch (ArgumentException argumentException)
{
}
catch (System.Exception exception)
{
}
return new HttpResponseMessage(responseStatus);
}
}
Now create your ILibrary Interface and declare the Implemented Methods:
public interface IMyLibrary
{
string GetApiUri(string server);
Task<HttpResultObject<U>> SetAppMethodGetParametersAsync<U>(string server, string method, Dictionary<string, object> parameters, CancellationToken token) where U : class;
}
Declare your Dependency Injection in your startup.cs in the ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyLibrary, MyLibrary>();
}
Now, if you want to you use your library in some Razor Component or Page just inject it like this:
#inject IMyLibrary _myLibrary
#code
{
private async Task MyHttpGetCall()
{
var cts = new CancellationTokenSource();
var result = await _myLibrary.SetAppMethodPostParametersAsync<HttpResultObject<MyCustomObject>>("auth", new Dictionary<string, object>(), cts.Token);
if (result.IsSuccesful)
{
//whatever you want to do
}
}
}
And that is all!, those are the 2 ways to connect your front-end web site developed with Blazor Web Assembly App to your Backend the same way you does with Angular or React.

Resources