I'm trying to get result from my minimal API who configured in endpoints of my MVC web application
my Get action configured like this :
endpoints.MapGet(
"HO-CFDZU4/api/Currency/Get",
[PermissionAuthorize(PermissionName.ReadCurrencyDictionary)]
async ([FromServicesAttribute] CurrencyService curency) =>
{
var result = await DataSourceLoader.LoadAsync(curency.Get(), new DataSourceLoadOptions());
return Results.Ok(result);
});
As result i get response with object where property names changed to lowercase, and its not suit for me.
I want to get exactly same name in same case like i return form action.
To get similar effect in MVC i used this code :
services
.AddMvc()
.AddFluentValidation(x => x.RegisterValidatorsFromAssembly(AppDomain.CurrentDomain.GetAssemblies().Where(x => x.FullName.Contains("ApplicationCore")).Single()))
.AddMvcLocalization()
.AddMvcOptions(options =>{})
.AddRazorRuntimeCompilation()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
Which setup property naming policy for Json while using action in controllers, and i dont know how to setup same policy for minimalApi.
What Ive tried is to set [JsonPropertyName(name)] And it working good but we have lot of classes and i looking for more global solution.
I also tried configure JsonOptions globally like this:
services.Configure<JsonOptions>(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
But it do nothing
Use JsonOptions from Microsoft.AspNetCore.Http.Json namespace (docs):
services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.PropertyNamingPolicy = null;
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
UPD
If your application uses both Minimal APIs endpoints and MVC ones, then you try to configure options from both namespaces:
services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
{
options.SerializerOptions.PropertyNamingPolicy = null;
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
services.Configure<Microsoft.AspNetCore.Mvc.JsonOptions>(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
Related
I'm building a .NET 7 MVC app that uses Azure AD for Authentication but calls out to another API to add additional claims to the Identity.
This worked great when I defined the Claim Transformation statically, but I'd like to register the Claim Transformation as a singleton instead so that it can manage its own token lifetime to the API.
This is what the code looked like to add the claims when the transformation was static:
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.Configure<MicrosoftIdentityOptions>(
OpenIdConnectDefaults.AuthenticationScheme, opt =>
{
opt.Events.OnTokenValidated = async context =>
{
if (context.Principal != null)
{
context.Principal = await ClaimsAPI.TransformAsync(context.Principal);
}
};
});
This works, but the Claim Transformation class can't store a bearer jwt, and would need to get a fresh one every time, wasting a ton of resources.
this is the closest I've come to getting it to work as a singleton, but it causes plenty of issues
builder.Services.AddSingleton<ICLaimsAPI, ClaimsAPI>();
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.Configure<MicrosoftIdentityOptions>(
OpenIdConnectDefaults.AuthenticationScheme, opt =>
{
opt.Events.OnTokenValidated = async context =>
{
if (context.Principal != null)
{
context.Principal = await builder.Services.BuildServiceProvider()
.GetRequiredService<IClaimsAPI>()
.TransformAsync(context.Principal);
}
};
});
This generates a seperate copy of each singleton, which doesn't really work for obvious reasons.
How can I inject my service so that it adds the claims correctly?
EDIT: Solved!
I had to do some slight tweaks to #Acegambit's code. here is my working solution for postierity, just in case someone in the future needs to solve a similar problem.
builder.Services.AddSingleton<IClaimsAPI, ClaimsAPI>();
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddOptions().AddSingleton<IConfigureOptions<MicrosoftIdentityOptions>>(provider =>
{
var ClaimsAPI = provider.GetRequiredService<IClaimsAPI>();
return new ConfigureNamedOptions<MicrosoftIdentityOptions>(OpenIdConnectDefaults.AuthenticationScheme, opt =>
{
opt.Events.OnTokenValidated = async context =>
{
if (context.Principal != null)
{
context.Principal = await ClaimsAPI.TransformAsync(context.Principal);
}
};
});
});
This took a little digging into the IServiceCollection extension methods. Looking at the implementation of Configure<TOptions> it really doesn't do a whole lot other than call .AddOptions() and register a singleton of type IConfigureOptions so I think you can pull out that code and do it yourself like so:
builder.Services.AddSingleton<IClaimsAPI, ClaimsAPI>();
builder.Services.AddOptions();
builder.Services.AddSingleton<IConfigureOptions<MicrosoftIdentityOptions>>(provider =>
{
var claimsApi = provider.GetRequiredService<IClaimsAPI>();
return new ConfigureNamedOptions<MicrosoftIdentityOptions>(string.Empty, options =>
{
// TODO: insert your logic to set the context.Principle here
// using the claimsApi that should resolve from the provider above
});
});
There's already an answer but I figure it would be good to show how options has evolved to make this scenario a bit more terse:
builder.Services.AddOptions<MicrosoftIdentityOptions>(OpenIdConnectDefaults.AuthenticationScheme)
.Configure<IClaimsAPI>((options, claimsApi) =>
{
options.Events = new()
{
OnTokenValidated = context =>
{
context.Principal = claimsApi.Transform(context.Principal);
return Task.CompletedTask;
}
};
});
I have a asp.net core web api application that runs great like this:
class Program
{
/// <summary>
/// Main method
/// </summary>
static void Main(string[] args)
{
// pass this as a parameter to specify what database I will like to use
Func<IServiceProvider, IMyDatabase> GetDatabaseFactory = provider =>
{
// used for testing purposes. In production I will use the real DB
return new MyDummyDatabase();
}
// create on a method so that it can be unit tested
WebApplication app = CreateMyAppWebApiApplication(GetDatabaseFactory);
// run application
app.Run();
}
}
And here is the method CreateMyAppWebApiApplication.
/* I removed a lot of stuff I just want to illustrate the idea. Moreover, I have hardocoded a lot of stuff for testing purposes. Once it works I will move it to a configuration file.*/
static WebApplication CreateMyAppWebApiApplication(StartAspDotNetParameters parameters)
{
var builder = WebApplication.CreateBuilder();
builder.WebHost.ConfigureKestrel(k =>
{
var port = 8888;
k.Listen(System.Net.IPAddress.Any, port, listenOptions =>
{
// Enable support for HTTP1 and HTTP2 (required if you want to host gRPC endpoints)
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps();
});
});
#region Add IoC dependencies
// .. code some dependencies I need for the controlers
#endregion
// add controllers
var mvcBuilder = builder.Services.AddControllers();
// serialize enums as string
mvcBuilder.AddJsonOptions(opts =>
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())
);
// Configure Swagger/OpenAPI. More info: https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
// code to configure...
});
WebApplication? app = builder.Build();
#region Configure the HTTP request pipeline.
// first middleware to intercept swagger.json file
// I have hardocded the path for testing purposes
app.Use(async (HttpContext context, Func<Task> next) =>
{
if (requestUrl.EndsWith("myapp-swagger.json"))
{
var content = File.ReadAllText(#"T:\repos\.....\myapp-swagger.json.json");
context.Response.ContentLength = content.Length;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(content);
return;
}
else
{
// else execute next middleware
await next();
}
});
// enable swagger
app.UseSwagger();
// change swager endpoint
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "documentation";
c.SwaggerEndpoint("/myapp-swagger.json", "MY API");
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
// This will run the application
//// execute endpoint
//app.Run();
return app;
#endregion
}
The things important to note about this method are:
// I changed swagger default endpoint
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "documentation";
c.SwaggerEndpoint("/myapp-swagger.json", "MY API");
});
// AND
// first middleware to intercept swagger.json file
// I have hardocded the path for testing purposes
app.Use(async (HttpContext context, Func<Task> next) =>
{
if (requestUrl.EndsWith("myapp-swagger.json"))
{
var content = File.ReadAllText(#"T:\repos\.....\myapp-swagger.json.json");
context.Response.ContentLength = content.Length;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(content);
return;
}
else
{
// else execute next middleware
await next();
}
});
Anyways that code works great.
Now here is the problem:
When I try to run that same code from a Tests project like this:
[Fact]
public async Task TestUserPermissions_IntegrationTest()
{
// pass the same dummyDatabase
WebApplication app = CreateMyAppWebApiApplication(provider =>
{
// used for testing purposes. In production I will use the real DB
return new MyDummyDatabase();
});
loginWorked = false;
var taskLogin = Task.Run(async () =>
{
// make sure app starts by waiting 5 seconds
await Task.Delay(5000);
using var client = new HttpClient();
var json = #"{ 'username':'tono', 'password':'myPassword'}".Replace("'", "\"");
var content = new StringContent(json, Encoding.UTF8, "application/json");
var result = await client.PostAsync("https://localhost:8888/api/LoginController/Login", content);
Console.WriteLine(result.StatusCode);
loginWorked = result.StatusCode == 200;
});
// run application
app.Run();
await taskLogin ;
Assert.True(loginWorked);
}
The app runs but I am not able to consume the API when running in the Test project
Finally found the answer. The controllers where not being found because I was running the project from a different assembly. This solution for stackoverflow made my Test pass:
https://stackoverflow.com/a/59121354/637142
In other words I ended up adding this code
// add controllers
var mvcBuilder = builder.Services.AddControllers();
// add controllers from this assembly. This is needed in case we are calling this method from unit tests project.
mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(typeof(MyCustomController).Assembly));
Problem is that I get Functions runtime is unreachable error after adding AddAccessTokenManagement() in startup.cs file. Also the list of fuctions in azure is empty. The best part is that from app insights I see that my cron job is beeing executed anyway, and token is working. When running my code in local enviroment there is no problem reported, deployments also seems to be fine. This is how I configure my http client to work with identity token:
private void ConfigureAccessToken(IFunctionsHostBuilder builder)
{
var IdentityServerUrl = "<serverUrl>"; ;
builder.Services.AddHttpClient();
builder.Services.AddAccessTokenManagement(options =>
{
options.Client.Clients.Add("cloud-service", new ClientCredentialsTokenRequest
{
Address = $"{IdentityServerUrl}/connect/token",
ClientId = _authorizationConfig.ClientId,
ClientSecret = _authorizationConfig.ClientSecret,
});
});
builder.Services.AddClientAccessTokenClient("internal-client", configureClient: client => { });
}
Worth to mention that this way of configuring it works with my Web API application.
Any ideas guys?
I found the answer by myself. Looks like token confiuration for azure functions differ from Web API. Working code below:
private void ConfigureAccessToken(IFunctionsHostBuilder builder)
{
var IdentityServerUrl = "<serverUri>";
builder.Services.Configure<AccessTokenManagementOptions>(o =>
{
o.Client.Clients.Add("cloud-service", new ClientCredentialsTokenRequest
{
Address = $"{IdentityServerUrl}/connect/token",
ClientId = _authorizationConfig.ClientId,
ClientSecret = _authorizationConfig.ClientSecret,
});
});
builder.Services.AddDistributedMemoryCache();
builder.Services.AddTransient<ITokenClientConfigurationService, DefaultTokenClientConfigurationService>(s =>
{
return new DefaultTokenClientConfigurationService(
s.GetRequiredService<IOptions<AccessTokenManagementOptions>>(),
null,
null);
});
builder.Services.AddHttpClient(AccessTokenManagementDefaults.BackChannelHttpClientName);
builder.Services.TryAddTransient<ITokenEndpointService, TokenEndpointService>();
builder.Services.TryAddTransient<IClientAccessTokenCache, ClientAccessTokenCache>();
builder.Services.AddTransient<IAccessTokenManagementService, AccessTokenManagementService>(s =>
{
return new AccessTokenManagementService(
null,
null,
s.GetRequiredService<IOptions<AccessTokenManagementOptions>>(),
s.GetRequiredService<ITokenEndpointService>(),
s.GetRequiredService<IClientAccessTokenCache>(),
s.GetRequiredService<ILogger<AccessTokenManagementService>>()
);
});
builder.Services.AddTransient<ClientAccessTokenHandler>();
builder.Services.AddClientAccessTokenClient("internal-client", configureClient: config => {});
}
I would like to change the default Cookie name for .AspNetCore.Antiforgery.xxx in ASP.NET Core 3.X MVC, however I do not seem to find any documentation on it. Is it even possible?
The only one I found to be able to alter was this:
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
options.ConsentCookie.Name = "GDRP";
});
This is achievable using AddAntiforgery. Here's an example taken from the docs and modified accordingly:
services.AddAntiforgery(options =>
{
options.Cookie.Name = "YourCookieName";
});
There's a useful page in the docs that lists the built-in ASP.NET Core cookies and where the configuration for each comes from.
For .NET 5.0 and higher
in ProjectRoot/Startup.cs class
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "your_cookie_name";
});
// ...
services.AddControllers();
}
ok, found it already, for those that are looking
services.AddAntiforgery(options =>
{
options.Cookie.Name = "my-x-name";
options.HeaderName = "my-x-name";
});
It will accept any string, need to validate if it works or if something else needs to be updated...
I'm trying to figure out the easiest way to test my Service Registrations method for my framework. I'm creating dynamic services my registration looks like so:
var messageHubConfig = new DynamicHubServiceConfiguration<Message, MessageDTO>();
messageHubConfig.SetDynamicHubOptions<AstootContext>(async (context, dto) =>
{
return await context.ConversationSubscriptions
.Where(x => x.ConversationId == dto.ConversationId
&& x.IsSubscribed)
.Distinct()
.Select(x => x.User.UniqueIdentifier)
.ToListAsync();
});
messageHubConfig.RequiresDynamicValidator = false;
messageHubConfig.EventMapping.AddCreateEvent(async (sp, obj, dto) =>
{
var conversationService = sp.GetService<IRestEzService<Conversation, ConversationDTO>>();
var conversationDTO = await conversationService.Get(new object[] { dto.ConversationId });
var hubTaskQueue = sp.GetService<IHubServiceTaskQueue>();
hubTaskQueue.QueueDynamicCreate(conversationDTO);
}).When(async (sp, dto) => {
var context = sp.GetService<AstootContext>();
return await context.Conversations.Where(x => x.Id == dto.ConversationId).Where(x => x.Messages.Count == 1).AnyAsync();
});
//Registers service with a hub
restConfiguration.RegisterRestService(typeof(IMessageDTOService),
typeof(MessageDTOService),
messageHubConfig);
Inside of my Register Rest Service Method I have a lot of different services Getting registered e.g:
services.AddTransient(restServiceType, (IServiceProvider serviceProvider) =>
{
var restService = (IRestEzService<TEntity, TDTO>)
ActivatorUtilities.CreateInstance(serviceProvider, restServiceImplementationType);
serviceOption.EventMapping?.Register(serviceProvider, restService);
return restService;
});
How can I be assure that my factory configuration is being registered properly, How can I create a Service Collection for testing?
Create a ServiceCollection,
var services = new ServiceCollection();
call your registration function and then assert that your restServiceType was added.
Next build a provider from the service collection, resolve the restServiceType
var provider = services.BuildServiceProvider();
var restService = provider.GetRequiredService(restServiceType);
and assert that it is created as desired.
The GetRequiredService extension method will throw an exception if the service is unable to resolve the target type.
Now that is based solely on what is currently being shown in your example as I am unaware of any other dependencies.
Based on #Nkosi's answer, a quick test that all Servives are wired up and in a particular order:
// Arrange
var services = new ServiceCollection();
// Act
var provider = services.AddBaseServices(); // Whatever service you have...
// Assert
Assert.AreEqual(27, provider.Count);
// Run this code once and copy the output into this test...
for(int i = 0; i < provider.Count; i++)
{
System.Diagnostics.Debug.WriteLine($"Assert.AreEqual(\"{provider[i].ServiceType.Name}\", provider[{i}].ServiceType.Name);");
}