IStartup isn't going to work with generic host which is the default in 3.0. It also conflicts with a few other patterns like ConfigureTestServices.
See https://github.com/aspnet/AspNetCore/issues/11001
@davidfowl
Yes!! Lets kill anything that is not convention based startup and have 1 way / signature to configure everything.
I'm a bit confused. How is removing an explicit interface killing anything not convention based startup? Usually in my experience an interface adds structure/convention, rather than kills it...
I also don't understand the comment:
It also conflicts with a few other patterns like ConfigureTestServices.
@javiercn said in https://github.com/aspnet/AspNetCore/issues/11001#issuecomment-500528103 that ConfigureTestServices is also going to be no longer required:
In 3.0 there is only one DI container and ConfigureTestServices is not required as configure methods are applied in order (which was the issue ConfigureTestServices was trying to solve).
ConfigureTestServices is meant for the common case, which is a Startup class without extending/implementing any inferface
It sounds like in 3.0, ConfigureTestServices becomes obsolete as well.
The convention for ConfigureServices allows multiple signatures. The convention we prefer is public void ConfigureServices(IServiceCollection services)
, but IStartup dictates public IServiceProvider ConfigureServices(IServiceCollection services)
I get it - basically, you prefer the fluent building to be internal to the Startup object, and the IStartup interface allows fluent external configuration (via escaping an IServiceProvider), which also makes it harder to make consistent mock-able POCO.
We can break the IStartup
interface instead of deprecating it. We need to remove the return type from ConfigureServices
@davidfowl I like this approach better, as it makes it easier to write architecture verification tests - I can then explicitly decouple my Pipeline pre-processing from my Pipeline processor from my Pipeline post-processor - these could all live in different (sets of!) assemblies. This is more or less the goals of more functional frameworks like F# Giraffe.
@davidfowl breaking IStartup will hurt existing apps upgrading to 3.0. Those apps were still going to work using the old WebHost.
Then create a IStartup2 interface and deprecate the old IStartup with an
Obsolete warning telling people to forward to IStartup2. A bit klunky, but
better than this ridiculous POCO approach (sorry, can't hide my true
feelings for what a mess that is).
On Mon, Jun 10, 2019 at 3:45 PM Chris Ross notifications@github.com wrote:
@davidfowl https://github.com/davidfowl breaking IStartup will hurt
existing apps upgrading to 3.0. Those apps were still going to work using
the old WebHost.—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/aspnet/AspNetCore/issues/11050?email_source=notifications&email_token=AADNH7LQHRHK6CLBOJG2ZQTPZ2VMBA5CNFSM4HWV5FPKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXLARUY#issuecomment-500566227,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADNH7OGVGGDVZL5FBUQL5DPZ2VMBANCNFSM4HWV5FPA
.
@jzabroski The poco story supports more scenarios. I rather not have something called IStartup2
, because it sounds awful 😄. How IStartupBase/Core something other than 2?
IStartupCore is clever and good.
When you say the poco story supports more scenarios, are you thinking of deploying a Startup engine in multiple contexts, e.g. non-MVC, such that the Startup class does not even have to have a reference to MVC? Good in theory, strange in practice: Startup will reference a concrete WebHostBuilder, right? So how do you eliminate the private dependency on Microsoft.ASP.NET? Would you just use C# pre-processor #ifdef commands and compile two versions of your Bootstrap/Startup engine?
I ask because this is an interesting user story I mostly hate in existing IoC projects: the whole "extend the IoC container via extension method" thing seems quite amateurish. It started with Ninject and that's been the standard design pattern ever since, and I think it was a mistake that needs to stop. The fundamental problem is that IoC frameworks are tightly coupled to the framework runtime hosting policy and how it handles assembly load contexts, threads and processes. - This is what extension methods SHOULD be doing, not some Ninject.Mvc thing you jam into your bootstrap.
I can give you a concrete user story to illustrate my point: How do you have a Controller method that delegates to PLINQ and passes the currently authenticated user to the child threads spawned by PLINQ?
Can you give me a concrete user story to demonstrate why a "poco story supports more scenarios" that are actually useful and solve real problems I have?
I can give you a concrete user story to illustrate my point: How do you have a Controller method that delegates to PLINQ and passes the currently authenticated user to the child threads spawned by PLINQ?
What does that have to do with anything?
Can you give me a concrete user story to demonstrate why a "poco story supports more scenarios" that are actually useful and solve real problems I have?
The Startup class is a bootstrapper, fundamentally it can't really have any dependencies inject into it since it's the one that has to configure more services. That limitation is even more evident in 3.0 as we disallow injecting anything into the startup constructor that wasn't already provided by the hosting (configuration and IHostEnvironment etc). To support injecting services, you can inject into the Configure method directly. That's the main use case that is broken by using the IStartup interface.
What does that have to do with anything?
I discovered two nights ago that Principals are dependency injected in ASP.NET Core, so they are bound to a scope, not a thread, so my point is likely irrelevant. Sorry.
The Startup class is a bootstrapper, fundamentally it can't really have any dependencies inject into it since it's the one that has to configure more services. That limitation is even more evident in 3.0 as we disallow injecting anything into the startup constructor that wasn't already provided by the hosting (configuration and IHostEnvironment etc). To support injecting services, you can inject into the Configure method directly. That's the main use case that is broken by using the IStartup interface.
An interface is not a dependency; the Startup class is the dependency.
Dependency inversion principle: Depend on abstractions, not implementation details.
Interfaces do NOT break use cases. They enable use cases.
If the goal is to disallow configuring the Microsoft DI plumbing from binding IStartupCore to a Startup class, then add a feature to Microsoft DI to throw exceptions on certain invalid bindings.
Startup is a bootstrapper not a dependency. It's the composition root for the application level container (well Configure is really).
Having an interface is orthogonal to the fact it is a bootstrapper.
The interface allows a generic constraint on the fluent interface for setting the composition root. You even agree by saying:
well Configure is really
Netting it out, here are the correct tasks:
I agree there should be an interface as the startup class must adhere to a contract. Using convention is fine for people who don't wish to implement the interface, but it requires you "know" the convention or run the application then look at the resulting exception to learn what the convention is. Even then you don't have exact types, so you probably end up needing to review the documentation. I don't see how providing a strongly typed contract is not ideal. Can someone explain to my why convention is a better approach? And if so why aren't we using convention in ASP.NET core for things like identity, principal, etc. vs contracts (interfaces). As I said before I don't think the two need to be mutually exclusive.
Yeah, it's a bit like the convention allowed in a C# foreach statement:
foreach
needs is a GetEnumerator()
method that returns an object that has a MoveNext()
method returning a bool, and a Current
property returning an objectIEnumerator
, which is the same as implementing GetEnumerator()
, MoveNext()
and Current
There is a convention, but the convention _also_ has a contract: IEnumerator.
Hope this parallel design helps explain what I'm looking for.
I understand why people want for the interface and we’ll add one back. I was just clarifying some misconceptions about why both patterns exist and why IStartup is a bootstrapper/composition root and not a dependency
Another suggested task (will update my previous comment as well):
@davidfowl It's an interesting side discussion I wish we could just have via instant message. I do not really understand (and thus cannot agree or disagree) with your stance that a Startup class is not a dependency. I agree that it is a bootstrapper/composition root, but at least when deploying code to environments where the host exposes a lot of static state, the Startup is actually the most critical dependency of all:
update https://github.com/aspnet/AspNetCore.Docs/blob/master/aspnetcore/fundamentals/dependency-injection/samples/2.x/DependencyInjectionSample/Startup.cs to use IStartupCore as best practice
No, I don't think we'll do that.
@davidfowl It's an interesting side discussion I wish we could just have via instant message. I do not really understand (and thus cannot agree or disagree) with your stance that a Startup class is not a dependency. I agree that it is a bootstrapper/composition root, but at least when deploying code to environments where the host exposes a lot of static state, the Startup is actually the most critical dependency of all:
It's OK to disagree, it's not worth a further discussion.
@davidfowl @Tratcher we need to find the minimal thing needed in 3.0 as time is running very short. Can we do this in 3.1, since we weren't actually going to remove anything, just obsolete it?
shrug either way. As you say, it's just guidance.
So there is no way to add an IStartup instance in net core 3? Why? I don't want to pass a type, I already have an instance I am using, I want to use that. Impossible now in .net core 3?
Jeff, you can still pass an instance but the contract isn't public. "Why" isn't worthy of discussion according to the team, despite the hundreds of lost hours of productivity this simple thing will cause across developers worldwide.
Jeff, you can still pass an instance but the contract isn't public. "Why" isn't worthy of discussion according to the team, despite the hundreds of lost hours of productivity this simple thing will cause across developers worldwide.
How do I pass an instance? I only see the UseStartup<T>
This should be listed as a breaking change in the docs. I came here because the following working code now throws:
System.NotSupportedException : Microsoft.AspNetCore.Hosting.IStartup isn't supported
```c#
public class CustomWebApplicationFactory
where TEntryPoint : class
{
protected override TestServer CreateServer(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing").UseStartup
return base.CreateServer(builder);
}
}
public class TestStartup : Startup
{
public TestStartup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
: base(configuration, webHostEnvironment) { }
public override void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(...);
base.ConfigureServices(services);
services.AddSingleton(...);
}
}
public class Startup : StartupBase { ... }
```
I personally prefer the strongly typed class and am not sure why the convention based approach is pushed in this instance when it isn't for Controller
's.
@RehanSaeed Welcome to my world, where most of ASP.NET has become pleasant save for a few small issues, like this insane one where you have to "know" the signature of the Startup class and can't just hit control+. in order to generate the correct interface to implement, and then instantly get your integration tests working for almost free.
Maybe someone will one day change ASP.NET's mind.
I think the part that annoys me most about this whole thing is we went from WebExtensionEx Nuget package and literally not needing to care at all about how our Startup is constructed _unless we really cared to customize it_, to this mess. It went from something most people didn't even have to care about, to something most people can't just implement an interface to get going with.
An interface is completely reasonable. It just can’t be the same IStartup (I think i mentioned this already). So we can look at that. The thread kinda digressed into throwing stones at the other approach.
So I’ll rename this thread to something constructive so we can make progress.
I ended up here because of the:
IServiceProvider ConfigureServices(IServiceCollection services);
signature.
But, it took a bit of effort. The analyzer response from the interface-supporting
return services.BuildServiceProvider();
sent me on a goose chase, until I came across: David's comment https://github.com/aspnet/AspNetCore/issues/14587#issuecomment-537963678
The comment was helpful, but avoiding the chase somehow would have been more helpful. Perhaps an analyzer warning on ConfigureServices
, or [Obsolete]
on IStartup
, if this decision is final.
I guess I'm saying, in addition to the issue this topic has centered around, I'm also interested in the original title :D . I just want to make sure that part of the issue, which may be simplest and separate from the other debate, isn't lost.
How hard is it to add an interface for these conventions? Can I do it? I don't understand why this issue is just sitting here doing nothing. It seems like a 15 minute thing? I realize we all have lives and stuff, but the amount of time myself and friends have wasted trying to learn these conventions by reading docs vs. just having IntelliSense DO IT is a bit silly :P
How is this for an interface? It would also allow Roslyn analyzers to be run that warn people not to call BuildServiceProvider, one of @davidfowl pet peeves.
```c#
public interface IStartupConvention
{
IConfiguration Configuration { get; }
///
/// Completely optional. method to configure the app's services. A service is a reusable component that provides app functionality. Services are registered in ConfigureServices and consumed across the app via dependency injection (DI) or ApplicationServices.
///
///
/// 1. Completely optional.
/// 2. Called by the host before the Configure method to configure the app's services.
/// 3. Where configuration options are set by convention.
/// 4. Do not call BuildServiceProvider in this method.
///
void ConfigureServices(IServiceCollection services);
///
/// adas
///
///
void Configure(IApplicationBuilder app, IWebHostEnvironment env);
}
```
Assigning this to myself for the 5.0 time frame for investigation.
Instead of being helped by IntelliSense I always find myself reading over and over the documentation and other people's code, and delving with run time errors.
Sorry for being direct but this use of conventions over strongly typed contracts is just frustrating. It makes me miss the pre-Core ASP.NET frameworks.
@jzabroski
Jeff, you can still pass an instance but the contract isn't public
How? I tried with no success. Would you share a code snippet to do that?
To have the most flexibility the startup system should allow a developer to:
IServiceCollection
instance.It makes sense that the framework is designed to dissuade from these practices, but the reality is there are certain scenarios that this flexibility is needed, so they can't be completely eliminated. I don't have time to elaborate on these scenarios right now, but if there is enough interest I can make time.
@jzabroski
Jeff, you can still pass an instance but the contract isn't public
How? I tried with no success. Would you share a code snippet to do that?
@arialdomartini I already proposed how, above: https://github.com/dotnet/aspnetcore/issues/11050#issuecomment-579484487
I designed the contract based on the magic implicit contract described here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-3.1#environments
Don't you just love magic numbers?
- The startup type should allow a custom DI container to be built using the provided
IServiceCollection
instance.
@JustinKaffenberger The whole point is that your custom DI container should not build its own type universe. I doubt your proposal would ever get accepted. David has said repeatedly its a mistake to build a service provider yourself and the host should do that work. Just use a "Membrane pattern" and have your DI container be wrapped by Microsoft DI. Let Microsoft DI manage the web host, and then create a cross-wiring layer so that your container can get objects it needs from Microsoft DI.
Specify the instance of the startup type, not just the type.
This would be the thing to enable.
The startup type should allow a custom DI container to be built using the provided IServiceCollection instance.
You can do this on your own once you can pass the instance.
Being unable to provide an instance makes testing a real nightmare.
Replacing
UseStartup<Startup>()
with
UseStartup<TestStartup>()
with an innocent
public class TestStartup : Startup {}
just breaks the API. Why?
Isn't it a violation of the Liskov Substitution Principle? It seems it's abusing reflection so much that the result is not OO anymore.
@davidfowl thanks, that's what we're doing now to get this to work. It just felt sketchy since I had to look into the ASP.NET source to make sure manually registering the instance would be allowed. So I guess my suggestion is to just make this functionality more obvious.
How is this for an interface? It would also allow Roslyn analyzers to be run that warn people not to call BuildServiceProvider, one of @davidfowl pet peeves.
public interface IStartupConvention { void ConfigureServices(IServiceCollection services); void Configure(IApplicationBuilder app, IWebHostEnvironment env); }
The Configure()
method currently accepts additional parameters, fed by DI. It would be hard to put that into an interface, I believe.
Proposal for specifying the startup instance is here https://github.com/dotnet/aspnetcore/issues/19809#issuecomment-661675618
I still have strong bad feelings about having such a core class being exposed to throwing runtime exceptions instead of rendering compile time errors.
So, I would strive for an interface just providing a mandatory IStartup::ConfigureServices()
, method similar to what @jzabroski suggested:
c#
interface IStartup
{
/// <summary>
/// This method gets called by the runtime once.
/// Use this method to add application specific services
/// to the <paramref name="services"/> collection.
/// </summary>
/// <param name="services">
/// Collection of services, pre-filled with systems services.
/// Supplement with additional, application specific services.
/// </param>
/// <param name="configuration">
/// Runtime environment configuration data.
/// </param>
void ConfigureServices(IServiceCollection services, IConfiguration configuration);
}
... and have Configure()
be an optional, arbitrary method decorated by an attibute, e.g. WebConfigurationMethodAttribute
.
I would suggest to rename Startup
to WebConfiguration
because that's what the class actually does. _"Startup"_ actually is the Program
class.
So, the interface name I suggest would correspondingly be IWebConfiguration
.
@SetTrend I appreciate the sentiment but renaming the concept of Startup to IWebConfiguration
may feel pure is an arbitrary change that adds no value and increases confusion and concepts. We already have configuration, dependency injection, hosts, startup classes.
... and have Configure() be an optional, arbitrary method decorated by an attibute, e.g. WebConfigurationMethodAttribute.
Instead of calling your method Configure? What's the benefit?
This feature exists for a small subset of people that want a contract and don't need injection of dependencies in to Configure. To that end, I think something like:
C#
public interface IStartupConvention
{
void ConfigureServices(IServiceCollection services, WebHostBuilderContext context);
void Configure(IApplicationBuilder app);
}
Seems like it would make sense as a strawman proposal. Some things I would like to make clear:
Once I got my head around the configure services and configure app call, I decided to architect a delegate pattern. So I have a service runner class which has a constructor that takes an interface that allows hooking into various parts of the pipeline. The service runner calls the host builder, configure services, etc. then calls the delegate to give it an option to setup additional services. Same for configuring the application and various other bits in the setup pipeline.
Whether this should be built into the core framework, that's hard to say. But for now, I don't have any concern about IStartup
being deprecated. Leave it up to the consumer of the framework if/how they want to delegate pipeline setup functions to a delegate/interface.
renaming the concept of Startup to
IWebConfiguration
may feel pure is an arbitrary change that adds no value and increases confusion and concepts.
On the contrary. Renaming would _decrease_ confusion. "Startup" can be anything and nothing. Program.cs
is a startup class, too, for instance. Without reading the docs or intimate knowledge of ASP.NET, one wouldn't know what the class does or what's it's about.
Instead of calling your method Configure? What's the benefit?
Compared to the current implementation, the benefit – again – would be that you wouldn't need a crystal ball to tell what the method is or does, nor do you need external sources to tell what the name of the method must be for the system to work. Interfaces, contracts ... that's what object oriented programming - and robust programming - is about. It's not a game of hide-and-seek.
This feature exists for a small subset of people that want a contract and don't need injection of dependencies in to Configure. To that end, I think something like:
public interface IStartupConvention { void ConfigureServices(IServiceCollection services, WebHostBuilderContext context); void Configure(IApplicationBuilder app); }
If dependency injection for Configure()
is not mandatory, that's a fair enough interface.
On the contrary. Renaming would decrease confusion. "Startup" can be anything and nothing. Program.cs is a startup class, too, for instance. Without reading the docs or intimate knowledge of ASP.NET, one wouldn't know what the class does or what's it's about.
We'll have to agree to disagree on that one.
@pranavkm @Pilchie can we write a code fix for this?
Is the codefix to warn about the use of IStartup
and turn it into IStartupConvention
? That's not already a type, is it?
Is the codefix to warn about the use of IStartup and turn it into IStartupConvention? That's not already a type, is it?
The code fix is the warn about malformed or missing Configure (or malformed ConfigureServices).
@SetTrend
The Configure() method currently accepts additional parameters, fed by DI. It would be hard to put that into an interface, I believe.
Ideally, the additional parameters would be injected through the constructor, which luckily is not part of the interface.
@arialdomartini:
Sounds a good idea to me. And that's absolutely what I would suggest striving for, too.
However, there is a subtlety that needs to be dealt with: some of the objects initialized in Configure()
may require arguments provided by services, themselves being provided by dependency injection.
For the sake of this discussion let's assume there are two kinds of services: (a) system provided, and (b) custom provided.
System provided services may be injected with the constructor. Custom provided services currently are being initialized in ConfigureServices()
.
That's the current approach as it is today.
Supposed you switch to using the interface as suggested by @davidfowl (and I sincerely hope you do), this would cause a conceptual change:
IServiceCollection
and be stored in local fields, too.Then all required services would be available in Configure()
*) through local fields.
A sample implementation then would look like this:
```c#
internal class WebConfiguration : IStartupConvention
{
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
private WebHostBuilderContext _context;
private Options _options;
internal WebConfiguration(IConfiguration configuration, ILogger logger)
{
_configuration = configuration;
_logger = logger;
}
public void ConfigureServices(IServiceCollection services, WebHostBuilderContext context)
{
_context = context;
services
.AddSingleton(_options = _configuration.GetSection("AppSettings").Get<Options>())
.AddControllers()
;
}
public void Configure(IApplicationBuilder app)
{
if (_context.HostingEnvironment.IsDevelopment()) app.UseDeveloperExceptionPage();
DbContext.ConnectionString = Regex.Replace(_options.DbConStr, @"\|DataDirectory\|\\?", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\");
app
.UseHttpsRedirection()
.UseRouting()
.UseAuthorization()
.UseEndpoints(endpoints => endpoints.MapControllers())
;
}
}
<br/>
_<sup>*)</sup> I'd like to stress again that `Configure`, just like `Startup`, is an unspecific name, not giving meaning to the purpose of the method and the class itself._
_It's a no-go, according to [Microsoft naming guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines)._
_I strongly advice to overhaul the naming._
<hr/>
Instead of shoving private fields back and forth in such a class — wouldn't it make sense, be much easier and more streamlined to just put all of it into one single function (defined by interface), i.e. omit the constructor and omit a second function? `IServiceCollection` would require a generic `T Get<T>()` method then, that's all.
If required, the programmer then may split that function in whatever sub functions (s)he believes is appropriate.
Such interface and corresponding sample class may look like this:
```c#
interface IWebAppConfiguration
{
void ConfigureApp(IServiceCollection services, IApplicationBuilder app, WebHostBuilderContext context);
}
```c#
internal class WebAppConfiguration : IWebAppConfiguration
{
public void ConfigureApp(IServiceCollection services, IApplicationBuilder app, WebHostBuilderContext context)
{
Options options;
services
.AddSingleton(options = services.Get<IConfiguration>().GetSection("AppSettings").Get<Options>())
.AddControllers()
;
DbContext.ConnectionString = Regex.Replace(options.DbConStr, @"\|DataDirectory\|\\?", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\");
if (context.HostingEnvironment.IsDevelopment()) app.UseDeveloperExceptionPage();
app
.UseHttpsRedirection()
.UseRouting()
.UseAuthorization()
.UseEndpoints(endpoints => endpoints.MapControllers())
;
}
}
```
Ideally, the additional parameters would be injected through the constructor, which luckily is not part of the interface.
What parameters would those be?
I have a feeling that based on the suggestions made, there's a fundamental disconnect and understanding on how Startup works. One that I have expressed in this thread several times but seems to have gotten lost in the details. Startup cannot support injected arguments that are not already materialized outside of host's dependency injection context. It can't because the container hasn't been built yet. The DI container has 2 phases:
Startup
)This is why we have 2 methods, and this is why the phases exist. Hopefully that explains why things are split.
@SetTrend I'd like to make it clear that there's low to no appetite to change anything as drastically as you have specified it here (in fact what you suggest doesn't even work practically but I rather not get into it):
Let me try to explain the reasoning behind this:
The next big change to the startup experience will solve a bunch of these problems all at once and not introduce new changes without huge benefits.
You don't consider a number of blog posts written immutable and more important that robustness, testability and program design, do you? This is about the next generation of ASP.NET. SemVer: "MAJOR version when you make incompatible API changes".
- It suddenly increases the number of ways to do things.
What in particular does it increase?
- It increases the concept count
Yes, from bad to good. The current concept is based on runtime-typing and otherwise typeless reflection. That's not a concept, that's a design flaw. If runtime typing was such a great feature, then why would they have invented TypeScript over JavaScript?
- It clashes with our existing concept of configuration
So what? Just take a deep breath and improve.
It doesn't improve the authoring experience
What makes you believe that compile time type checking wouldn't improve programming experience? Did you ask a DevOp manager about this?
It doesn't improve any of the harder problems that exist around the Startup class like:
- Abstracting pieces of startup away (Composing bigger startup classes from smaller pieces)
lt doesn't keep you from doing this, either.
- Not being async (in fact this makes it worse)
Gee ... So go on and make it async. The current version isn't async, either.
- It doesn't improve any of the harder problems that exist around the Startup class like:
- Abstracting pieces of startup away (Composing bigger startup classes from smaller pieces)
-- @davidfowl
lt doesn't keep you from doing this, either.
@SetTrend
Actually, when I think of composing bigger startup classes from smaller pieces, I think of F# Giraffe web framework, which is built on top of ASP.NET but uses F# pipelines to hide much of the ASP.NET Startup glue. It's actually not even about composing bigger startup classes from smaller pieces, it's about describing the pieces as pure computations with no side effects, so that there is a top-down explanation of the API and behavior that can be stepped through without reflection. Instead, Giraffe uses an HttpHandler that is a composition of functions similar to what is provided by IApplicationBuilder (and, in fact, to bootstrap itself, Giraffe uses IApplicationBuilder). Below is an example, originally taken from Scott Hanselman but his blog post example is also featured on the Giraffe github page :
```f#
open Giraffe.HttpHandlers
open Giraffe.Middleware
let webApp =
choose [
route "/ping" >=> text "pong"
route "/" >=> htmlFile "/pages/index.html" ]
type Startup() =
member __.Configure (app : IApplicationBuilder)
(env : IHostingEnvironment)
(loggerFactory : ILoggerFactory) =
app.UseGiraffe webApp // THIS IS IT. This is basically the whole configuration.
And, if you scroll down further on their page, they describe a F# Giraffe program _that doesn't even use a Startup class_.
```f#
open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.DependencyInjection
open Giraffe
let webApp =
choose [
route "/ping" >=> text "pong"
route "/" >=> htmlFile "/pages/index.html" ]
let configureApp (app : IApplicationBuilder) =
// Add Giraffe to the ASP.NET Core pipeline
app.UseGiraffe webApp
let configureServices (services : IServiceCollection) =
// Add Giraffe dependencies
services.AddGiraffe() |> ignore
[<EntryPoint>]
let main _ =
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(
fun webHostBuilder ->
webHostBuilder
.Configure(configureApp)
.ConfigureServices(configureServices)
|> ignore)
.Build()
.Run()
0
@jzabroski I don't see how this fixes the problems I'm talking about but F# syntax is succinct 😄
@SetTrend maybe it wasn't clear but I was explaining that the next turn of the crank for startup will tackle all of those problems.
I think I would need specific examples of the problem, then, because the F# approach does solve several problems with structural composition (the composition is also not free; the trade-off is someone has to stitch together manually the whole pipeline). At the same time, you wrote the framework, so where I see problems you may see "RTFM" :-). For example, one I did not mention above, because I wanted to be brief, is that through using a Hindley-Milner type system, you can write code that manipulates an existential type through a universally quantified function. One particular use case for this technical concept is the question of how you can write open generic controllers that can then be "interpreted" by other parts of the framework, like a Swagger endpoint. In ASP.NET Core Swagger, if you try to UseSwagger()
on a WebApi project with open generic controllers (like an open generic SearchController<T>
), then you cannot just "plug-in Swagger". In this sense, many "web parts" in ASP.NET Core are monolithic since they do not allow you to create abstract interpretations of the execution and use those abstract interpretations in other places of the program. But all Swagger is doing is "Create a new HttpHandler that returns html from an HttpHandler that accepts/returns json."
Moving to Backlog to consider for 6.0 at this point.
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
Most helpful comment
This should be listed as a breaking change in the docs. I came here because the following working code now throws:
```c# : WebApplicationFactory();
public class CustomWebApplicationFactory
where TEntryPoint : class
{
protected override TestServer CreateServer(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing").UseStartup
return base.CreateServer(builder);
}
}
public class TestStartup : Startup
{
public TestStartup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
: base(configuration, webHostEnvironment) { }
}
public class Startup : StartupBase { ... }
```
I personally prefer the strongly typed class and am not sure why the convention based approach is pushed in this instance when it isn't for
Controller
's.