Can I use FlurlClient with Asp.Net Core TestServer? - asp.net-web-api

We are using FlurlClient in a few projects and familiar with their fluent interface.
We now want to use it in asp.net core integration tests using TestServer.
The example from
http://asp.net-hacker.rocks/2017/09/27/testing-aspnetcore.html
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
I was going to change code to
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
var httpClient = _server.CreateClient();
_client = new FlurlClient(httpClient);
and use all FlurlClient methods/extensions.
But then I noticed
Is it possible to use Furl.Http with the OWIN TestServer? which described that more work is required in owin implementation.
Is approach for Asp.Net Core TestServer similar? Or is it simplified?

It's much simplified, and your proposed change is exactly right. The question you linked to is old and my answer contains information that's no longer relevant in 2.x. (I have updated it.) In fact, the ability to provide an existing HttpClient directly in a FlurlClient constructor was added very recently, and with this specific use case in mind.
Here's an extension method I use as a replacement for CreateClient; you might find it handy if you do this a lot:
public static class TestServerExtensions
{
public static IFlurlClient CreateFlurlClient(this TestServer server) =>
new FlurlClient(server.CreateClient());
}

Related

how to use services before app build in .net core 6.0

I have earlier achieved this .net 3.1. But it couldn't be possible with .Net 6 because of startup.cs removed.
I have registered a few services,
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var appSettings = builder.Configuration.GetSection("AppSettings").Get<AppSettings>();
builder.Services.AddScoped<IEncryption, Encryption>();
//Here I need to get the IEncryption Service, and call the method in this service to encrypt/decrypt the connection string to pass to DBContext Service.
builder.Services.AddDbContext<CatalogDbContext>(options => options.UseNpgsql(
appSettings.ConnectionString));
var app = builder.Build();
Earlier in .NET 3.1, I used BuildServicProvider() to get the Encryption service, and call the methods in that service to do the required logic then got the proper connection string I wanted that would be passed to the DBContext service on the next line.
Now, .NET 6/7 is forced to use the services only after app = builder.Build(); so, I can't register the DBCOntext after the build() method.
How can I solve this case? Any recommended approach to do this in .NET 6/7?
You still can useStartup.cs in .net 6
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services); // calling ConfigureServices method
var app = builder.Build();
startup.Configure(app, builder.Environment); // calling Configure method
And then you can use ConfigureServices and Configure methods to register your services before building.
You didn't need to use BuildServiceProvider in .NET Core 3.1 either. AddDbContext has an overload that provides access to an IServiceProvider instance :
builder.Services.AddDbContext<CatalogDbContext>((services,options) =>{
var myOwnDecrypter=services.GetRequiredService<IMyOwnDecrypter>();
var cns=myOwnDecrypter.Decrypt(appSettings.ConnectionString,key);
options.UseNpgsql(cns);
});
or, if you use the ASP.NET Core Data Protection package :
builder.Services.AddDataProtection();
...
builder.Services.AddDbContext<CatalogDbContext>((services,options) =>{
var protector = services.GetDataProtector("Contoso.Example.v2");
var cns=protector.Unprotect(appSettings.ConnectionString);
options.UseNpgsql(cns);
});
or, if IConfiguration.GetConnectionString is used :
builder.Services.AddDataProtection();
...
builder.Services.AddDbContext<CatalogDbContext>((services,options) =>{
var conn_string=services.GetService<IConfiguration>()
.GetConnectionString("MyConnectionString");
var protector = services.GetDataProtector("Contoso.Example.v2");
var cns=protector.Unprotect(conn_string);
options.UseNpgsql(cns);
});
That said, it's the configuration provider's job to decrypt encrypted settings, not the service/context's. ASP.NET Core's configuration allows using multiple different configuration sources in the same host, not just a single settings file. There's nothing special about appsettings.json. That's just the default settings file name.
You can add another settings file with sensitive contents with AddJsonSettings. That file could use the file system's encryption, eg NTFS Encryption, to ensure it's only readable by the web app account
You can read settings from a key management service, like Hashicorp, Azure Key Vault, Amazon Key Management etc.
You can create your own provider that decrypts its input. The answers to this SO questino show how to do this and one of them inherits from JsonConfigurationProvider directly.
Important Caveat: In general, my suggestion below is a bad practice
Do not call BuildServiceProvider
Why is bad? Calling BuildServiceProvider from application code results in more than one copy of singleton services being created which might result in incorrect application behavior.
Justification: I think it is safe to call BuildServiceProvider as long as you haven't registered any singletons before calling it. Admittedly not ideal, but it should work.
You can still callBuildServiceProvider() in .Net6:
builder.Services.AddScoped<IEncryption, Encryption>();
// create service provider
var provider = builder.Services.BuildServiceProvider();
var encryption = scope.ServiceProvider.GetService<IEncryptionService>();
// use service here
or alternatively
builder.Services.AddScoped<IEncryption, Encryption>();
var provider = builder.Services.BuildServiceProvider();
using (var scope = provider.CreateScope()) {
var encryption = scope.ServiceProvider.GetService<IEncryptionService>();
// use service here
}
Alternative:
You can still use the classic startup structure in .Net6/7. We upgraded our .Net3.1 projects to .Net6 without having to rewrite/restructure the Startup()

Calling internal (Endpoint) function in WebAPI

I am using Hangfire to execute recurring jobs in my web API and I use System.Web.HttpContext.Current.Server.MapPath in the "RoutineMethod" function.
But the problem is it throws object null exception.
I searched the problem and found that recurring jobs don't use http threads, they use background threads.
Now to resolve this problem I need to call my internal (endpoint) using httpclient.
But to do that I need to give URL of the Web API (to generate URI). So is there any other way to call internal function using httpclient.
My current code:
public static async Task<IHttpActionResult> RoutineTask()
{
//It was like this before.
//await new DemoController().RoutineMethod();
//await new DemoController().RoutineMethod2();
//I am planning to do this.
using (var client = new HttpClient())
{
//But I need to give URI which I don't think is a good idea.
var uri = new Uri("http://localhost/DemoApp/api/DemoController/RoutineMethod");
await client.GetAsync(uri);
}
return new DemoController().Ok();
}
The short answer is no. The HttpClient, as its name implies, requires an http connection.
There are no smell issues with storing service connection information in a configuration file.
However, to expand on Nkosi's comment, it appears that since your code can create an instance of DemoController, that it must have a reference to that controller's project or even be in the same project. I would extract the interesting code into a library or service that all areas needing the information can reference.

How to use X509 certificate with the Nest Elastic Client

I am currently upgrading from Elastic Search 1.7 to 5.2. I know there is no upgrade path, which is fine. One problem we had originally is with Nest and ElastiSearch.Net there was no way to attach an X509 certificate as it only had the ability to use Basic Authentication. To get around that we made copies of the existing github repos and modified the code directly to allow it. This ultimately is what kept us from upgrading for so long since we couldn't just use the nuget packages, because we now had custom code.
Now that we are upgrading I'm trying to find out if this was ever remedied. Or, at the very least are there any hooks that we can use to get the ElasticClient(in Nest) or the ElasticLowLevelClient (in ElasticSearch.Net) to take in a certificate and pass it on when making the call.
Another option, is to use a PUT request to create the Index on initial creation, which is where we are needing the certificate. The issue we have there is we require the use of the AutoMap method since we have some custom attributes added on our models, and need those to go in on Index creation. I'm not sure if there is a way to generate that result of AutoMap for a given model to JSON, and just use a webclient to attach the certificate.
Let me know if you need anymore details.
It's possible to customise the connections that both NEST and Elasticsearch.Net use all the way back to 1.x. This is done by providing your own implementation of IConnection to the ConnectionSettings instance that is passed to the ElasticClient constructor.
First create your custom IConnection; it's easiest to derive from HttpConnection
public class HttpConnectionWithClientCertificate : HttpConnection
{
protected override HttpWebRequest CreateHttpWebRequest(RequestData requestData)
{
var request = base.CreateHttpWebRequest(requestData);
// add the certificate to the request
request.ClientCertificates.Add(new X509Certificate("path_to_cert"));
return request;
}
}
then pass this to ConnectionSettings
var node = new Uri("http://localhost:9200");
var connectionPool = new SingleNodeConnectionPool(node);
var connection = new HttpConnectionWithClientCertificate();
var settings = new ConnectionSettings(connectionPool, connection);
var client = new ElasticClient(config);

How to use .NetCore 1.0 and IdentityServer3 together?

I am trying to make my application work with our IdentityServer3. The unsolvable to me seems that there is no IdentityServer3.AccessTokenValidation package for .NetCore. I have to validate the token (other software works in that manner, but using .NetFramework with no trouble), what are my options or perhaps I didn't research properly?
I would really love to see
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://identity.identityserver.io",
RequiredScopes = new[] { "api1", "api2" }
});
For Asp.Net Core use IdentityServer4.AccessTokenValidation. Just grab it via your NuGet package manager. Remember IDS3 and IDS4 are just implementations of a common set of protocols. Your OP can be written in Asp.Net Core (eg with IDS4) and your WebApi can still be using MVC5 (using IDS3 AccessTokenValidation) and vice versa scenario applies. In the IDS4 version of this middleware you will need to use ScopeName and AdditionalScopes to achieve your goals

Without the helpers from WebAPI, this can't be the best way to return status codes from MVC6, can it?

While I'm aware that MVC6 isn't released, it looks like it's missing many features of WebAPI and even MVC5. Can I assume that this isn't the way that it'll look on release?
[HttpPost("")]
public async Task<ActionResult> Post(Visit newVisit)
{
var username = User.Identity.GetUserName();
newVisit.UserName = username;
if (await _repository.AddVisitAsync(newVisit))
{
Response.StatusCode = (int) HttpStatusCode.Created;
return Json(newVisit);
}
return new HttpStatusCodeResult((int)HttpStatusCode.BadRequest);
}
Notice the casts in the Reponse.StatusCode and the HttpStatusCodeResult (I do miss just returning Ok(...), Created(...), etc.
Some features from MVC 5 and Web API 2 have not yet been brought over to MVC 6 (which includes Web API). Logging issues at https://github.com/aspnet/Mvc/issues is the right place to request any missing features. Please check for existing issues because many issues are already tracked.
Please note that several APIs got renamed when MVC and Web API got merged because we didn't want to have duplicate APIs, so even though an exact API match might not be there, it could just have a new name.

Resources