I am running a dotnet core 2.2 app with IdentityServer4 installed using Nuget. When I build a docker container and run, all works fine. When I deploy this container to my Google Kubernetes Engine cluster it fails on startup with the following:
{
insertId: "18ykz2ofg4ipm0"
labels: {
compute.googleapis.com/resource_name: "fluentd-gcp-v3.1.0-nndnb"
container.googleapis.com/namespace_name: "my_namespace"
container.googleapis.com/pod_name: "identity-deployment-5b8bd8745b-qn2v8"
container.googleapis.com/stream: "stdout"
}
logName: "projects/my_project/logs/app"
receiveTimestamp: "2018-12-07T21:09:25.708527406Z"
resource: {
labels: {
cluster_name: "my_cluster"
container_name: "app"
instance_id: "4238697312444637243"
namespace_id: "my_namespace"
pod_id: "identity-deployment-5b8bd8745b-qn2v8"
project_id: "my_project"
zone: "europe-west2-b"
}
type: "container"
}
severity: "INFO"
textPayload: "System.NullReferenceException: Object reference not set
to an instance of an object.
at
IdentityServer4.Services.DefaultUserSession.RemoveSessionIdCookieAsync()
at
IdentityServer4.Services.DefaultUserSession.EnsureSessionIdCookieAsync()
at
IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
"
timestamp: "2018-12-07T21:09:17Z"
}
As I mentioned, this works perfectly locally, and when running inside a docker container, only when within Kubernetes do I see these errors.
I'm not sure what I've missed here with kubernetes, but any help very much appreciated.
This vexed me in the last few days. I suspect it has something to do with the recent deprecation of the former mechanism in app builder configuration:
app.UseAuthentication().UseCookieAuthentication(); <-- no longer valid and apparently will not even compile now.
This has been replaced the following in the ConfigureServices section:
services.AddAuthentication("YourCookieName")
.AddCookie("YourCookieName", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(30.0);
});
While I am not sure what the exact breaking change is for identityserver4, after cloning identityserver4 components and debugging I was able to isolate the the constructor for DefaultUserSession takes an IHttpContextAccessor that was arriving as null:
The constructor in question:
public DefaultUserSession(
IHttpContextAccessor httpContextAccessor,
IAuthenticationSchemeProvider schemes,
IAuthenticationHandlerProvider handlers,
IdentityServerOptions options,
ISystemClock clock,
ILogger<IUserSession> logger)
{ ...
The following solution gets you past the error, though hopefully identityserver4 will make this moot in a near future release.
You need to add an IHttpContextAccessor service in ConfigureServices:
public override void ConfigureServices(IServiceCollection services)
{
... other code omitted ...
services.AddScoped<IHttpContextAccessor>(provider => new
LocalHttpContextAccessor(provider));
... other code omitted ...
}
The LocalHttpContextAccessor is just a private class in the configuration class as seen here:
private class LocalHttpContextAccessor : IHttpContextAccessor
{
public IServiceProvider serviceProvider { get; private set; }
public HttpContext httpContext { get; set; }
public LocalHttpContextAccessor(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
this.httpContext = null;
}
public HttpContext HttpContext
{
get
{
return this.httpContext;
}
set
{
this.httpContext = null;
}
}
}
The problem is that at the point of configuring services, there is no current context to set, so I set it in a using block in the app builder configuration stage:
public override void Configure(IApplicationBuilder app,
IHostingEnvironment env)
{
app.Use(async (context, next) =>
{
IHttpContextAccessor httpContextAccessor;
httpContextAccessor = context.RequestServices.GetRequiredService<IHttpContextAccessor>();
if (httpContextAccessor is LocalHttpContextAccessor)
{
((LocalHttpContextAccessor)httpContextAccessor).httpContext = context;
}
await next();
});
... other code omitted ...
app.UseIdentityServer();
That will set the http context prior to running identity server code which fixes the bug. The scoped service should be created individually for each request. I've only recently made the full plunge into .net core from .net framework, so if there are scope or DI issues in that code that might lead to leaks or bad life-cycle, I'd appreciate the input. That said, at the very least that code keeps identity server 4 from crashing with the core 2.2+.
Related
We have a Web API Core application that use the EF Core with the SQL Server in a backend. I am trying to update one of the database tables whenever the Web API service starts (in a Startup.cs Configure method)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, SomeDBContext dataContext, ILoggerFactory loggerFactory)
{
.......................
// Automatically perform database migration
dataContext.Database.Migrate();
PopulateSomeTable(dataContext);
}
private async void PopulateSomeTable(SomeDBContext context)
{
var someTables = context.SomeTables;
if (someTables != null && (await someTables .CountAsync()) == 0)
{
someTables .Add(new Entities.SomeTable
{
someProperty1= 20,
someProperty2 = "Something",
someProperty3 = DateTimeOffset.Now,
});
await context.SaveChangesAsync();
}
}
However, when I try to access the context I get this error
Cannot access a disposed object. A common cause of this error is
disposing a context that was resolved from dependency injection and
then later trying to use the same context instance elsewhere in your
application. This may occur if you are calling Dispose() on the
context, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context
How can I fix it?
Thank you in advance
You need to replace private async void PopulateSomeTable(SomeDBContext context) to private async Task PopulateSomeTable(SomeDBContext context). Replace async void to async task. Check this link for more info.
remove all your code from startup, it is not the best place to make db migration, and add only this code
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext context)
{
if (env.IsDevelopment())
{
context.Database.EnsureCreated();
}
}
add this code to ConfigureServices of startup
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver());
services.AddDbContext<SomeDBContext (options =>
options.UseSqlServer(Configuration.GetConnectionString("CategoryDbConnection")));
to populate data add code like this OnModelCreating of SomeDBContext
modelBuilder.Entity<SomeTable>().HasData(
new SomeTable{ ....
});
to migrate classes to database tables follow this link
https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=vs
I am migrating a web app from asp.net mvc to .net core (.net 5), and this has got me stuck.
The site is configured in IIS to accept request from multiple URLs like site1.example.com and site2.example.com. Each site has its own database, accessed through entity framework core.
In the old .net framework, I was able to use one of the events in the global.asax.cs to parse the incoming request URL and lookup the correct tenant database from a configuration file. I'm trying to set up something similar in asp.net core mvc.
Here's the relevant part of my ConfigureServices method in the startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>((serviceProvider, dbContextBuilder) =>
{
var tenantIdentifier = serviceProvider.GetRequiredService<ITenantIdentifier>();
var connectionString = Configuration.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
dbContextBuilder.UseSqlServer(connectionString);
}, ServiceLifetime.Scoped);
//other services configured below...
}
Then the tenant identifier looks like this:
public interface ITenantIdentifier
{
string GetCurrentTenantId();
}
public class UrlTenantIdentifier : ITenantIdentifier
{
readonly IHttpContextAccessor _httpContextAccessor;
readonly ILogger<UrlTenantIdentifier> _logger;
public UrlTenantIdentifier(IHttpContextAccessor httpContextAccessor, ILogger<UrlTenantIdentifier> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public string GetCurrentTenantId()
{
//_httpContextAccessor is null here
//logic below for parsing URL and finding if we're site1 or site2
}
}
Is there a correct way of doing this now that I'm not aware of? How can I set up the entity framework database context for dependency injection when I don't know the connection string key until runtime? Am I going to be stuck configuring separate sites and virtual directories in IIS?
Refactor the DbContext to override the OnConfiguring member. Inject configuration and context accessor and perform configuration there.
public class myDbContext : DbContext {
private readonly ITenantIdentifier tenantIdentifier;
private readonly IConfiguration configuration;
public myDbContext(IConfiguration configuration, ITenantIdentifier tenantIdentifier) {
this.configuration = configuration;
this.tenantIdentifier = tenantIdentifier;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
var connectionString = configuration
.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
optionsBuilder.UseSqlServer(connectionString);
}
}
Trying to access the request context at the time the DbContext is being created/initialized is too early in the request flow to get access to the desired information. It needs to happen after the context has already been initialized and injected.
public void ConfigureServices(IServiceCollection services)
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>(); //Simplified since configuration is internal
//other services configured below...
}
Reference DbContext Lifetime, Configuration, and Initialization
I've just upgraded my ASP.NET Core WebApi project from .NET Core 2.2 to 3.1.
I've fixed up all of the compile-time errors, upgraded my Nuget packages, and I can now run the app.
However, When I call Build() on my IHostBuilder, I get the following exception:
InvalidOperationException: Unable to resolve service for type
'Microsoft.AspNetCore.Http.RequestDelegate' while attempting to
activate 'MyProject.Api.Middleware.ExceptionHandlerMiddleware'.
The Middleware it's referring to is pretty standard.
ExceptionHandlerMiddleware.cs
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlerMiddleware> _logger;
public ExceptionHandlerMiddleware(RequestDelegate next, ILogger<ExceptionHandlerMiddleware> logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
// redacted
}
}
The rest of my app initialisation is fairly standard and I didn't change much going from 2.2 to 3.1 (2.2 was working).
I did change from services.AddMvc() to services.AddControllers().
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder =>
{
builder.UseSerilog().UseStartup<Startup>();
})
.ConfigureLogging((context, logging) =>
{
logging
.AddConfiguration(context.Configuration.GetSection("Logging"))
.AddConsole()
.AddDebug();
});
}
}
It's also worth mentioning that the ConfigureServices() method in Startup.cs is being called and runs fine, but Configure() never runs. The Build() method always kills the app before it gets to Configure().
My Startup's Configure method signature looks like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
I ran into the same problem today and resolved it as follows:
In .net core 2.2 I added the middleware to the application builder and also added the middleware to the service collection. Apparently adding the middleware to the service collection is no longer required and results in the exception you posted.
In my case removing the line
services.AddSingleton<MyMiddleware>();
resolved the issue.
The solution for this problem contains two main elements:
.NET Core 3.0 introduced a change regarding service provider validation.
My code is registering too many classes, causing the validation to fail.
The solution to my problem was to introduce the following code in Program.cs:
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.UseDefaultServiceProvider(opt =>
{
// this overrides the default service provider options
// so that it doesn't validate the service collection (which raises exceptions)
})
[ ... ]
}
Thanks to other answers that directed my attention to my Startup.cs.
Full explanation
I use Scrutor to scan assemblies and auto-register classes. Scrutor is finding and registering my Middleware classes such as ExceptionHandlerMiddleware.
It was doing this in .NET Core 2.2 as well as 3.1. So why did it only break in Core 3.1?
Because .NET Core 3.0 introduced the Generic Host as the new default way to build a host.
The code now contains a part to enable ValidateOnBuild by default under the development environment. This caused my ServiceProvider to validate on build. And it couldn't resolve RequestDelegate because I didn't register that.
.UseDefaultServiceProvider((context, options) =>
{
var isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
Here is the answer for those who use .netCore 6 :
First Remove Property of RequestDelegate :
private readonly RequestDelegate _next;
And then remove Your Constructor for RequestDelegate :
public SomeClassName (RequestDelegate next)
{
_next = next;
}
finally , here is the Invoke method :
public class SomeMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
Console.WriteLine("Hi I am Middleware in .net6");
await next(context);
}
}
As #user1796440 said,I could reproduce your issue by using services.AddSingleton<MyMiddleware>();.
To fix it,you need register middleware like below:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseMiddleware<ExceptionHandlerMiddleware>();
//...
}
I am using EF Core in my ASP.NET MVC Core (v1) application. And I notice while hosting my app on production for testing, the IIS Server normally recycles quite frequently due to reaching its memory limit.
I wanted to verify if the way I am using my dbContext in my application is valid and is not creating any memory leaks in the background. I have read some similar posts on SO where people recommend to dispose the context object after using it.
But I used it via dependency injection as follows.
Startup.cs class snippet:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<Context>();
}
Context.cs class snippet:
public class Context : IdentityDbContext<ApplicationUser>
{
private IConfigurationRoot _config;
private IHttpContextAccessor _HttpContextAccessor;
public Context(IConfigurationRoot config, DbContextOptions options, IHttpContextAccessor HttpContextAccessor)
: base(options)
{
_config = config;
_HttpContextAccessor = HttpContextAccessor;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["ConnectionStrings:ContextConnection"]);
}
}
Does the services.AddDbContext<Context> inject a shared instance of the context, which results in a buildup of all entities queried over time and therefore is eating up memory?
EDIT: I also have a couple of the following singleton instances as follows:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<ILoggerService, LoggerService>();
services.AddSingleton<IEmailSender, EmailSender>();
Thanks!
In my case, In action filter and some middlewares, i used a IServiceProvider that created with services.BuildServiceProvider() like this:
public class DependencyManager{
static class IServiceProvider ServiceProvider{ get;set;}
}
public class SomeMiddleware{
public void SomeMethod(){
var someServiceInstance = DependencyManager.ServiceProvider.GetService<SomeService>();
}
}
Because of this, all scoped objects that created to inject this service are not linked to any request and will not disposed when request ends. I fixed this with using RequestServices under HttpContext:
public class SomeMiddleware{
public void SomeMethod{
var someServiceInstance = context.RequestServices.GetService<SomeService>()
}
}
Is it possible to "disable" authentication in ASP.NET Core application without changing its logic?
I have a .net website which uses an external identity server app for authentication.
Anyway I would like to be able to mock the authentication when I'm developing it (ASPNETCORE_ENVIRONMENT = Development), airing access to all actions ignoring the authorization attributes.
Is it possible to do it just mocking some services in the service collection?
On updating to net core 3.1, the mvc AllowAnonymousFilter was not working for us any more. We found conditionally adding a custom IAuthorizationHander to be the simplest way forward to conditionally bypass auth.
eg.
/// <summary>
/// This authorisation handler will bypass all requirements
/// </summary>
public class AllowAnonymous : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
context.Succeed(requirement); //Simply pass all requirements
return Task.CompletedTask;
}
}
Then register this handler conditionally in Startup.ConfigureServices.
private readonly IWebHostEnvironment _env;
public Startup(IWebHostEnvironment env)
{
_env = env;
}
public void ConfigureServices(IServiceCollection services)
{
{...}
//Allows auth to be bypassed
if (_env.IsDevelopment())
services.AddSingleton<IAuthorizationHandler, AllowAnonymous>();
}
Note AddAuthentication and AddAuthorization services are still registered and configured as per prod code (which is nice).
To allow our unit test to bypass auth, we added a new anonymous testbase with a startup class that added this line without any conditions. Nice and simple!
You can bypass authorization in development environment by applying AllowAnonymousAttribute to your endpoints.
Example 1 dotnet new webapi template, .NET 6 (ASP.NET Core 6) and newer
Use AllowAnonymous method in Program.cs to apply AllowAnonymousAttribute to all controllers:
if (app.Environment.IsDevelopment())
app.MapControllers().AllowAnonymous();
else
app.MapControllers();
Example 2 dotnet new webapi template, .NET Core 3.0 - .NET 5 (ASP.NET Core 3.0-5)
Use WithMetadata method in Startup.Configure() to apply AllowAnonymousAttribute to all controllers:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseEndpoints(endpoints =>
{
if (env.IsDevelopment())
endpoints.MapControllers().WithMetadata(new AllowAnonymousAttribute());
else
endpoints.MapControllers();
});
}
Example 3 dotnet new webapi -minimal template, .NET 6 (ASP.NET Core 6) and newer
Use AllowAnonymous method to apply AllowAnonymousAttribute to a minimal API endpoint:
var hiEndpoint = app
.MapGet("/hi", () => "Hello!")
.RequireAuthorization();
if (app.Environment.IsDevelopment())
hiEndpoint.AllowAnonymous();
Details
endpoints and app from the examples above, both implement IEndpointRouteBuilder which has multiple Map extension methods like MapControllers() and MapGet(...) that return IEndpointConventionBuilder.
WithMetadata (available since .NET Core 3.0) and AllowAnonymous (available since .NET 5) are extensions for IEndpointConventionBuilder and can be called upon the results of those Map methods.
AllowAnonymousAttribute's description from the docs:
Specifies that the class or method that this attribute is applied to does not require authorization.
Another solution you may want to consider is using the IPolicyEvaluator. This means that you can keep all the existing security elements.
public class DisableAuthenticationPolicyEvaluator : IPolicyEvaluator
{
public async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
// Always pass authentication.
var authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(), new AuthenticationProperties(), JwtBearerDefaults.AuthenticationScheme);
return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
public async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
{
// Always pass authorization
return await Task.FromResult(PolicyAuthorizationResult.Success());
}
}
In the Startup.cs, ensure this appears at the top of the ConfigureServices method. Eg.
public void ConfigureServices(IServiceCollection services)
{
if (env.IsDevelopment())
{
// Disable authentication and authorization.
services.TryAddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
}
...
Rather than Startup.cs (and thanks to the comments below) if you are using Core 3.1 and you wish to use the WebApplicationFactory, you can do the following:
public class MyWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
// Disable Authentication.
services.RemoveAll<IPolicyEvaluator>();
services.AddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
});
}
}
I've found sollution for this problem on illucIT Blog.
This code must work:
if (env.IsDevelopment()) {
services.AddMvc(opts =>
{
opts.Filters.Add(new AllowAnonymousFilter());
});
} else {
services.AddMvc();
}
It's tricky to give a detailed answer without more details on your end, but I have previously achieved this by conditionally registering:
the external authentication middleware
the global policy that requires an authenticated request
it looked something like:
public class Startup
{
public Startup(IHostingEnvironment env)
{
Environment = env;
}
public IHostingEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(x =>
{
if (!Environment.IsDevelopment())
{
var authenticatedUserPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
x.Filters.Add(new AuthorizeFilter(authenticatedUserPolicy));
}
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseStaticFiles();
if (!Environment.IsDevelopment())
{
// Register external authentication middleware
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
In my case, the authorization filter was applied globally, so every single action of the MVC app required an authenticated user.
If you have different requirements - fine-grained [Authorize] attributes on some actions - then you could probably achieve the same result by changing how the associated authorization policies are built. They could basically contain no requirements at all.
AuthorizationPolicy yourCustomPolicy = null;
if (Environment.IsDevelopment())
{
yourCustomPolicy = new AuthorizationPolicyBuilder().Build();
}
else
{
yourCustomPolicy = new AuthorizationPolicyBuilder()
// chaining appropriate methods to suit your needs
.Build();
}
In ASP.NET Core 6, we managed to disable the authorization without changing any other part from the productive code, just the following logic in Program.cs:
if (!builder.Environment.IsDevelopment())
{
app.MapControllers();
}
else
{
app.MapControllers().AllowAnonymous();
}
This is to clarify #Kirill Lutsenko's answer about the method he found on the IllucIT blog post (note that in my case this is for .NET Core 2.0. I see other answers saying the AllowAnonymousFilter method won't work in .NET Core 3.1):
The Startup class has an overloaded constructor. One of the overloads takes an IHostingEnvironment parameter. You need to use this version of the constructor.
In the Startup class create a property of type IHostingEnvironment. Call it, say, Environment. Then set that property in the constructor.
Then, in the ConfigureServices method, you can use Environment.IsDevelopment().
public class Startup
{
public Startup(IHostingEnvironment environment)
{
Environment = environment;
}
public IHostingEnvironment Environment { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
services.AddMvc(options =>
{
// This uses the Environment property populated in the constructor.
if (Environment.IsDevelopment())
{
options.Filters.Add(new AllowAnonymousFilter());
}
// Set other options here. For example:
options.ModelBinderProviders.Insert(0, new UTCDateTimeModelBinderProvider());
//...
});
//...
}
}
As a side note, in real life we use a different overload of the constructor, which takes both an IConfiguration object and an IHostingEnvironment object as parameters. That allows us to configure services based on an appsettings.json configuration file.
For example:
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
// Data access via Entity Framework
services.AddDbContext<ContainersDbContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("OrdersDatabase"));
});
//...
}
}