Reinforced.Typings
Reinforced.Typings copied to clipboard
Dependency Injection support or getting all implementations of an interface.
This question is related to my attempts here: #120 re: dynamically generating API view models and discriminating them. The goal is to build a project, get all registered implementations of a base view model, and create a discriminator between them. This will allow the creation of a new API view model to automatically trigger build issues in typescript, since those new view models will not have been added to the discriminator function on the front end.
I understand that the current TS build happens during build and not during Startup. The hope is to create dependency injected types by hooking into the build process via a middleware after startup.
Even without the dependency injection, if I were to get all of the implementations via Reflection, is there a way to create a discriminated union type?
Example dependency injection API below. I'm using the same IReturnValue
example from the linked issue #120 .
// Startup.cs
public void ConfigureServices(IServiceCollection services) {
services
.AddReinforcedTypings()
.UseConfiguration<TsConfiguration>();
}
// TsConfiguration.cs
public class TsConfiguration : IReinforcedTypingsConfiguration
{
private readonly IEnumerable<IReturnValue> _returnValues;
public TsConfiguration(
IEnumerable<IReturnValue> returnValues
) {
_returnValues= returnValues;
}
public static void Configure(ConfigurationBuilder builder)
{
// the usual stuff
builder.Global(config => {
config.UseModules();
config.CamelCaseForProperties();
config.AutoOptionalProperties();
});
builder.ExportAsDiscriminant("ApiReturnValueList", _returnValues);
}
}
// api-models.ts
export type ApiModelList =
MyFirstReturnValue |
MySecondReturnValue;
// my-models.ts
export type ApiModel = {
returnValue: ApiModelList;
}
So in which moment you'd like output files to be generated? On build? On startup?
No response
FYI I don't have the answer, but I still think there should be a way to access Dependendency Injection in order to build TypeScript types. Startup doesn't make sense for Publishing. Technically it is a Build process. So there should be a way in the build process to access the same (or similar) dependency pipeline. I have some thoughts, but I am unfamiliar with the codebase so not sure how compatible this system would be with the build process.
The Reinforced.Typings process in .Net Standard (not sure how this falls back to .Net Framework) should create its own Console Host with a configurable Startup. For which startup to use, there are a few options:
- Using the same startup as the main project, calling the same
ConfigureServices
fromStartup
, and once that is complete, run the TypeScript build. This is probably the easiest to configure, but would probably result in a lot of pointless calls to services we don't care about and maybe security concerns as users add DB connections and other sensitive configurations here. Additionally, if there is another round of startups within a CMS or something, this might not actually add all the services (looking at Orchard Core). - Using the same startup but not calling
Configure() or ConfigureServices()
, but callingConfigureReinforcedTypings()
. This would allow someone to create an extension likeservices.AddMyApiServices()
, which they would call from bothConfigureServices
andConfigureTypings
on theserviceCollection
. I don't think this is going to be compatible with the .Net Generic Host since I believe Configure and ConfigureServices are required entrypoints. - Using a different Startup kind of like the current way
ConfigureReinforcedTypings
functions, but instead use the standardConfigureServices
andConfigure
, one to add the common services (accessible via the same extension defined in userland in option2
) and one to build the configuration.
I work more in the runtime than build processes, so even though I might not have an answer, I don't think this feature request should be closed.
Well, it is possible to run RT without console. That is how. This code can be wrapped into separate console host, process, thread or whatever you'd like. So I would appreciate pull request enchancing RT functionality by implementing your idea, because still cannot get full understanding how it must work.
That's fine if someone wants to circumvent the normal build process, but I'm recommending updating the normal build process. Right now, the Console looks like this:
// Bootstrapper.cs
public static class Reinforced.Typings.Cli {
public static Main() {
// Do the work here
new TSExporter().export();
}
}
I recommend that if NETCORE (or if .Net Framework has support for Microsoft.Extensions.Hosting, cool) and Configuration includes <UseStartup>MyProject.TsConfiguration.Startup</UseStartup>
(or something) then (I'm just winging this)
using Microsoft.Extension.Hosting; // Generic Console host
public static class Reinforced.Typings.Cli {
public static Main() {
// Get value of UseStartup()
var userlandStartupDefinition = GetConfiguration("UseStartup");
var userlandStartup = GetType(userlandStartupDefinition); // or whatever
IReinforcedTypingsStartup reinforcedTypingsStartup;
if (userlandStartup is IReinforcedTypingsStartup rts) {
reinforcedTypingsStartup = rts;
}
var host = new HostBuilder()
.ConfigureServices(services => {
if (reinforcedTypingsStartup != null) userlandStartup.ConfigureServices(services);
services.AddHostedService<ReinforcedTypingsEntrypoint>();
})
.UseConsoleLifetime()
.Build();
await host.RunAsync();
}
}
// ReinforcedTypingsEntrypoint.ts
public class ReinforcedTypingsEntrypoint: IHostedService, IDisposable {
private readonly IServiceProvider _serviceProvider ;
public ReinforcedTypingsEntrypoint(IServiceProvider serviceProvider) {
_serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken) {
if (!cancellationToken.IsCancellationRequested) {
// enter scope, like you would in an HTTP request so all DI can access scoped services
using (var scope = _serviceProvider.CreateScope())
{
var exporter = scope.ServiceProvider.GetRequiredService<TSExporter>();
await exporter.Export(cancellationToken);
}
}
}
}
That is beautiful, but please provide PR
I'll try to circle back to it, but if you keep this issue closed then it will be buried in the "Closed Issues" section. Can you re-open and label it something you can recognize as awaiting requestor?
We are interested in generating discriminated unions as well.
@johnrom Even without the complexity of changing the build process or removing the need to repeat the constant value (as mentioned in #120), don't think we could have a quick-win solution that could simply generate an explicitly specified discriminated union?
For example [TsUnion(typeof(ImplementationA), typeof(ImplementationB))]
?
@pavel-b-novikov Would you be interested in a PR for this?
Tbh, I do not fully understand what TS output you expect with TsUnion
@lemoinem that would remove the magic of singly registering the dependency via dependency injection. You now have to maintain two lists of the same components, one via dependency injection, and one via TsUnion.
If I have something like IFormDataApiModel
, 25 forms on the website, register each of them via dependency injection, and intend to use them as typescript ajax form submission models, the point is that maintaining a list of IFormDataApiModel
s on the TypeScript side is going to be a bad experience over time. Maintaining that second list in C# wouldn't be any better in my opinion.
Edit for clarity: I'm not trying to argue against implementing something like what you described, just that it's not a solution for this issue, and it might be better to open a new one.
@johnrom To be clear, what I'm offering is basically something outside of the scope of using dependency injection. I'm looking something that would achieve a similar goal (i.e. a similar TS output), but would be easier to implement within the current code base.
PS: I though the issue focused on discussing solutions for discriminated unions. Sorry if I hijacked an unrelated issue, I'll move the discussion over to the other issue. I had a hard time understanding the goal of changing the build process and using DI. I think it's much clearer now. Thanks!
@lemoinem no worries! I created feature requests for things which will support the usage of discriminated unions, but I never created a request to implement them in the API, so I can understand your confusion. In my case, I'd be creating DU from Fluent Configuration instead of as an attribute, so if you create an issue specifically for DU, we can discuss there.