dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.35k stars 9.99k forks source link

Blazor unified project design #49079

Closed SteveSandersonMS closed 1 year ago

SteveSandersonMS commented 1 year ago

In .NET 8 we plan to add a new project template, Blazor Web Application, that covers all combinations of server-hosted projects (traditional Blazor Server apps, Blazor WebAssembly hosted, and the new unified architecture that allows use of Server, WebAssembly, and SSR in a single project). It will work by multitargeting over net8.0 and net8.0-browser.

However we still have to decide on the conventions for how such a project should be structured. The biggest question is what determines which files/references are included in which compilation?

Goals

Possible designs

I think there are two three main approaches to pick from, as depicted here (click to expand):

image

Benefits of approach A ("single project, exclude by default")

Its main drawbacks are that the concept of ClientShared is nonobvious (and I spent ages coming up with that name, as almost everything else fails to communicate the idea that you're making stuff available to both client and server whereas otherwise it's server only - better name suggestions are welcome, but don't just say "client" or similar).

Benefits of approach B ("single project, include by default")

Its main drawback is that it is incompatible with typical ASP.NET Core projects, at least until developers manually exclude everything that can't work in WebAssembly, and then as you work on the project you have to keep excluding more things or unintentionally include them in the WebAssembly build. In the above example, all the .razor components end up in the wasm build pointlessly, increasing its size just because it's painful to keep excluding things.

Benefits of approach C ("two projects")

Its main drawback is that it gives up the multitargeting-based way to call server-only code from components that are shared with WebAssembly. For example, with approaches A and B, you could use #if SERVER inside a component to have a block of code that calls AppDbContext directly, with an #else block that runs on WebAssembly and maybe does an HttpClient call. That wouldn't work with option C because the .Client project couldn't reference types that live in the server project (there's no project reference in that direction). It means developers have to go back to traditional interfaces+DI, e.g. IMyRepository with different implementations for server and WebAssembly, since they can't just use #if SERVER etc.

In terms of whether the two-project system is harder to understand for total .NET newcomers, I honestly don't know. An extra project is an extra concept, however multitargeting and filename conventions are probably even thornier extra concepts still.

Proposal

As you can probably tell, between options A and B I'm currently leaning towards option A, however I'm still undecided on a preference between A and C. In the long term, having a single project will probably be an essential element of https://github.com/dotnet/aspnetcore/issues/46400, which may be a major feature for Blazor in .NET 9. So I suspect that's a likely direction eventually, however it doesn't mean that developers necessarily benefit in .NET 8 - there's an argument for keeping a simpler project system in .NET 8 and giving the single-project-multitargeting-conventions system more bake time. But perhaps I'm missing something about why we need to do a particular thing now in .NET 8.

If you have any feedback on what is wrong or missing from this analysis, please comment below!

cc @dotnet/aspnet-blazor-eng

SteveSandersonMS commented 1 year ago

BTW the way I think option A could best be implemented would be declaring an MSBuild property BrowserCompilationRoot with default value ClientShared. This actually means we support both A and B, since B is just a special case where BrowserCompilationRoot is set to "empty" or . (and of course then people can just use a different folder name if they want).

SteveSandersonMS commented 1 year ago

Project and Package references (for options A and B only)

There's also the question of references to other projects/packages. The same concern applies: for the WebAssembly build, we really don't want to include anything unnecessary by mistake, because (1) it can bloat the app size, and (2) it could lead to accidental disclosure of sensitive assets.

I propose we introduce a rule: For projects that multitarget net8.0 and net8.0-browser, all project/package references need to be annotated with ClientOnly, ServerOnly, or ServerAndClient, otherwise we log a warning.

For example, this is what would be in the .csproj by default:

    <ItemGroup>
        <PackageReference ClientOnly="true" Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0-preview.7.23324.1" />
        <PackageReference ServerOnly="true" Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.0-preview.7.23324.1" />
    </ItemGroup>

... and if you add a new reference via tooling, then by default it won't have any such annotation, and so by default you'll see a warning like this:

image

The point of it being a warning is not to break unrelated tutorials (e.g., for EF, gRPC, auth, etc, especially for newcomers who might not yet know how to edit a csproj) but make clear that we expect developers to make a conscious choice about it at some point.

Update: None of this applies to option C. This is only a consideration for options A and B.

javiercn commented 1 year ago

@SteveSandersonMS Instead of having to annotate each and every package/reference, could we have some sort of more general rule about what references are allowed by default and what references are not allowed?

Something akin to what happens https://github.com/dotnet/sdk/blob/main/src/StaticWebAssetsSdk/Sdk/Sdk.StaticWebAssets.StaticAssets.ProjectSystem.props#L30

So that we can say something like

<PropertyGroup>
  <DefaultExcludedReferences>MyCompany.*</DefaultExcludedReferences>
</PropertyGroup>

For the vast majority of cases, if you even care about preventing a .dll from being sent down to the browser, it's likely going to be a dll from your org. If we can avoid you having to annotate Newtonsoft.Json, that would be great.

johndhunter commented 1 year ago

@SteveSandersonMS - you may already have got the solution to "I spent ages coming up with that name, as almost everything else fails to communicate the idea that you're making stuff available to both client and server", - "Unified" - the other options being (dare I say it) "Common" or "Combined"

SteveSandersonMS commented 1 year ago

@johndhunter Those are definitely reasonable suggestions too. "Common" was in fact an earlier iteration I used - the part about I thought may be worth changing is being more explicit (as in, what is in common with what, since the project maybe used for many things besides just Blazor so there are a lot of different concepts - MVC, Razor Pages, gRPC, auth, etc.).

SteveSandersonMS commented 1 year ago

For the vast majority of cases, if you even care about preventing a .dll from being sent down to the browser, it's likely going to be a dll from your org.

I'm not sure. While that's probably true for the "unintended disclosure" concern, it's not true for the "bloating the wasm payload" concern. The particular example you chose of Newtonsoft.Json is a great one to think about because that's precisely the top example of something you really don't want in your wasm bundle for no reason (it's very big) but @danroth27 said he has seen people include it by accident in existing Blazor WebAssembly apps.

jonlipsky commented 1 year ago

@SteveSandersonMS It would be great if it would also give warnings about transitive package dependencies. I know in my own usage, that's how a few large assemblies ended up in my Blazor WASM applications.

mrpmorris commented 1 year ago

What is the benefit of having all of these in a single project as opposed to what we have now?

I like the two separate projects because you can't accidentally send server classes to the browser.

Having them as separate projects is 100% clear and immediately obvious, whereas both of these approaches seem like hidden behaviour for which you have to discover the rules.

MackinnonBuck commented 1 year ago

@SteveSandersonMS I really like the ideas here! I definitely agree that Option A would be a better choice, purely because it requires intentionally making things available to the client.


Would the *.Client.* file name convention only apply within the ClientShared folder? I ask because, without reading any descriptions, the name ClientShared to me implied "everything in this folder is shared with the client". But I guess technically, what it means is "everything in this folder is shared with the client, unless a file uses the .Client file name convention, in which case it's actually not shared and only available on the client." That wasn't immediately intuitive to me because I wouldn't consider "available to the client only" to be a subcase of "shared between the client and the server."

Maybe an alternative to the .Client convention is another top-level ClientOnly folder? That way, there's a clear separation between what's "server only", "client only", and "shared between the server and client". Although, I can think of some downsides to that approach as well (there are two Program.cs files, there isn't just a single location for all client-available components, etc.).


The package reference idea seems good. My only concern there is that the mainstream scenario requires editing the project file manually to address warnings. Ideally, it would be nice to reach a point where the customer makes the decision at the time of adding the package (via the VS UI or a CLI option) rather than after the package was added.


@mrpmorris also brings up an interesting point. It does seem to me that a lot of the effort put into this design is trying to emulate features that would otherwise be achieved automatically by using multiple projects. Would separate .Client, .Server, and .Shared projects actually make things simpler because it's less for the customer to learn and less complexity for the framework to manage?

Perhaps the single project approach works well with MAUI because in that world, general application functionality is almost always applicable for each platform. And in the cases where it's not, there isn't as big a need to exclude it from a specific platform for bundle size and security reasons. Whereas, with Blazor, deciding where the code should run will end up being the first step for almost everything that gets added, whether it be Razor components, package references, etc.

And I could imagine the easiest way for the customer to make that decision would be to pick which project the thing they're adding is most relevant to, add it, and be done. Eliminating the "which project?" question actually just adds two more questions (which folder does this go in and what file name convention needs to be used?), and in the case of adding a package reference, adds a question in the form of a warning in the error list.

Not trying to say we shouldn't support the single project approach - I'm just playing the devil's advocate because I think these are interesting things to consider 🙂

codeus-de commented 1 year ago

My vote for option A - I think separation by folders is much more visible compared to searching parts of each file name for .Server or .Client in it. One Idea for the folder name, that instantly came to my mind as I read your description "...you're making stuff available to both client and server...": "ClientAndServer". And the next idea is to only have one more folder on the root level that is called "ServerOnly"

SteveSandersonMS commented 1 year ago

Thanks for the responses.

Following some interesting thoughts from @mrpmorris and @MackinnonBuck I've updated the description to include an "option C" with two projects. Thanks both of you for that - it's definitely helpful to question the assumptions we've made so far, whether or not that results in a radically different outcome. See updated description above for pros/cons of option C.

Please keep the feedback coming.

claudiobernasconi commented 1 year ago

I understand the intentions behind a one-project solution. However, one of the .NET "rules" I internalized is that a project has a build target, and whatever is in that project will be in the output DLL.

Of course, .NET has developed over time, and I assume (I'm not familiar with .NET MAUI) that there are already exceptions.

However, in the traditional ASP.NET Core world, where I am most familiar (and probably a majority of .NET developers), I believe option A and option B introduce concepts that are hard to explain and could be frustrating for beginners to learn.

I lean towards option C here.

One of the drawbacks is that you cannot use the same component server-side and client-side. I'm trying to figure out how often developers want to do that. Also, I try to stay away from using #if etc. to keep testing simpler. I might be biased, but I grew up with traditional .NET, where using interfaces and DI was one of the core principles, and I don't think it's bad using it for edge cases like this.

What I assume could be a common pattern is that we implement "presenter" components that receive data via parameters. We can then have different "functional components" (higher-order components) that load data and provide it to those "presenter" components. This way, we can use the same presenter components, client-side and server-side. With option C, that means having a shared class library including those presenter components. Maybe not very convenient, but explicit, and simple.

Generally speaking: When it comes to API calls and/or what goes into what DLL, I prefer a simple, straightforward concept with a little more typing/manual work over a complex solution where you might accidentally leak secrets or bloat your WASM assembly. Also, it's not a great experience when you do it wrong, and as a result, you might believe that WASM is slow, etc. when it just lets you make mistakes easily.

I took some time to come up with a fourth option, but I have nothing so far. If there is not a lot of pressure to have the single-project solution with .NET 8, I'd advocate learning more about how people use Blazor with .NET 8 and all its new capabilities. And use those learnings to create a single-project template with .NET 9.

jburman commented 1 year ago

I immediately gravitate towards approach C for its apparent simplicity and since it cleanly reflects the deployment boundary, but I can also see the benefits of approach A for small projects. I dislike approach B as it looks like it would be very challenging to upgrade a larger existing project, as well as to maintain it on an ongoing basis.

In thinking through one of my own projects, this is how I am imagining it would work…

Today I have an app that consists of server side (razor pages) for logging in, and then it boots up a Blazor server app that runs a bunch of admin pages as well as a product catalog (or catalogue for my friends across the pond 😀). When I upgrade to .NET 8, what I would like to do is the following:

The first two items hopefully don’t require any changes to the components or the server-side DI. The Wasm based catalog would require the following changes:

In reality, it’s a bit more complicated because I re-use the catalog in a hybrid app as well, so all of the catalog components are in a separate RCL, along with some extra CSS and JS. Access to the catalog is abstracted to a CatalogItemProvider. But that doesn’t change too much from above. I would be swapping in a CatalogItemProvider that uses the API for example.

Finally, while not something that I am looking to do, would approach C also make it possible to reference more than one client project from the server project and then use them on different pages?

DrLeh commented 1 year ago

I like C because it's what I'm doing on all of my applications already- separating Blazor WASM from the web API that's hosting it. Makes it perfectly clear without special conventions what files are used where. This project reference to the client app makes it clear that the shared logic is one-directional, that nothing in the server that isn't needed for the web app will ever be included in the UI because it's not in that project.

robertmclaws commented 1 year ago

Honestly the biggest problem with the old project layout was that you folks threw everything into subfolders instead of just keeping them at the root of the project.

Since just fixing that is not one of the listed options, I'm going to suggest Option C, but it needs a different naming convention, and a suggestion for clarity moving forward.

"Client" should ONLY be used to reference server API consumption libraries. Because, what if you put Web APIs in your Blazor back-end, that people from outside Blazor also need to call? (I don't recommend it, but people certainly do it.)

Then you will have "SomeWebApp.Client" and SomeWebApp.ApiClient", or some permutation of that... which does not bode well for design.

If you go with Option C, you should call it "BlazorWeb", and "BlazorWeb.FrontEnd". Because the app fka "Client" is unequivocally the "front end" of the application.

Under that name, there will be no confusing what the purpose is. No misunderstanding if it has API implementation classes to consume. Clean, simple, unmistakable.

My $0.02. Keep up the great work!

Markz878 commented 1 year ago

I currently also lean towards option C, because it makes all the magic very obvious. That template also needs a Shared project, because how else would the Client project get the DTO classes? So that makes it basically the WASM hosted project, which I do like.

The biggest issues I have with the WASM hosted model currently are that the default template doesn't use prerendering which makes the initial first paint timing too long. With Blazor getting SSR that should be the default now, and we just replace all .cshtml files with .razor files. Also, the data persistance model required by prerendering has too much seremony, I wish we could get a [Persist] attribute like Javier showed in one of the stand-ups, or maybe it could just work somehow automagically? And antiforgery tokens in the default template for form posts.

I use Damien Bowden's template that uses a cookie based BFF pattern, where client side authentication/authorization is handled by an "GetUserAuthenticationState" API call. This makes it possible to get prerendering to work with authentication (I don't remember why but I also had to ditch the CascadingParameter way of passing auth state and just cache the auth state in a singleton). Cookies can also be used to load for example saved form data in the prerendering phase. This is mandatory IMO because we don't want to wait for the WASM to load to access local storage, since it takes too long.

If all of this could be crammed into option A where Blazor Server and WASM could be used interchangeably, that would also be nice, but that would require some serious innovation, like for example using some attribute with source generators to turn data loading component functions into a http/short lived signalR call if run on WASM side (just spitballing here, how expensive is signalR connection creation compared to http call)? Then the project might work like you showed in your initial demo. Using #if directives might also work, but sounds like a testing nightmare..? Maybe borrow the Sveltekit concept that the default way to pass data from client to server is though form posts. Then if the WASM hasn't yet loaded, it would work as an MPA. But as has been said, this would require some serious magic under the hood that is not very obvious, and would probably run into some strange edge cases.

So all in all, I'm happy with option C and hope we get the improvements I mentioned here. I don't really care about the Blazor Server style project because it's relatively heavy on the server and the SPA stops working/resets when the server restarts or when there are network issues. Of course if the project could start as Blazor Server style and immediately transition seamlessly under the hood in to WASM mode, that would be ideal. I was hoping that the Auto-mode you mentioned would do this, but this has the same issues to be solved as I mentioned in the previous paragraph.

SteveSandersonMS commented 1 year ago

That template also needs a Shared project, because how else would the Client project get the DTO classes?

They can go in the client project, as classes there are available everywhere. If people want to add a third "shared" project they are always free to do so, but there's no technical requirement for one.

markrendle commented 1 year ago

Very very very much in the "two projects" camp here. I know we're kinda sorta competing with Node+SSR[React/Vue/SvelteKit/Angular] and those are all one "project", but they're horrible projects with mad quantities of config files and npm modules. Differentiating compilation based on folders or naming conventions within a single project has a whiff of magic strings about it, and having to deal with #if pragma in application code introduces additional complexity and confusion with IDEs needing to switch between build targets for code analysis, IntelliSense, etc.

davidroth commented 1 year ago

I am also voting for Option C. It solves many issues of A in a clear and ideomatic dotnet way. It does not introduce strange new and confusing conventions. I also dont see it as a drawback that C doesnt allow "#If Server" because that can easily grow into a giant hack. The IRepository aproach is a reasonable and clean alternative IMO.

mrpmorris commented 1 year ago

@SteveSandersonMS I'm still not sure of the real-world application here.

People who develop Server apps tend to like using DbContext and editing Entities directly, whereas people who write WASM apps obviously cannot do that so use API contracts and serve those from the server instead.

Using #if Server etc would effectively mean writing two different apps not only in the same project, but also in the .cs files themselves, which isn't really good for maintenance.

If the developer wanted to have their app switchable between Server and WASM, then the better approach would be for them to always have their UI work with DTOs, and have the services that provide the data registered differently on the WASM project compared to the Server project.

The WASM app would call an API to get the DTO, whereas a Server app would create the DTO from the DBContext.

Obviously this is a more complicated approach than directly editing Entities, but I think it is also much more manageable than mixing the code into a single project and marking which classes go to the Sever vs WASM and using #if directives for the cases where the class goes to both but behaves differently.

Ultimately, people would still be able to do WASM how they do it now, and do Server how they do it now. If they want code that is switchable between the two then they have the option to add the complexity of different behaviours themselves in whatever way they prefer (e.g. registering different services).

I find option C far less prescriptive in its approach. It allows people to come up with their own solutions (which coders love), and separates client/server code in a way that everyone is already familiar with because it's the way we've always done it.

marinasundstrom commented 1 year ago

I would vote for Option C.

I don't think that the "single project" is a road where we should go down. The MAUI model doesn't work for Blazor since there will be two apps that have different entrypoint, components, logic etc.

Separating out components based on render modes is not an easy task to do. The experience will be terrible. And I'd rather not that we overcomplicate it by hiding the implementation details.

Anyway, you will just import WebAssembly components into the base app - not the other way around. Keeping the render modes in separate projects is the best option.

And it will be easier for the ones building the tools to create a better experience for developers.

alexei-lazari commented 1 year ago

I would vote for Option C. It is the simplest path in my opinion. Already using this and is relative simple to understand and configure, inclusive references.

ADefWebserver commented 1 year ago

I vote for Option C. The reason is I mostly create Blazor Server projects and only use Blazor Web Assembly for PWA apps. The devs on my team have no problems understanding the current single project Blazor Server structure. It only takes 2 minutes to explain the multi-project Web Assembly structure. In my opinion Option C already works.

Pinox commented 1 year ago

@SteveSandersonMS

Personally I get the hat that you put on for Blazor & ASP.NET but what about if you make the project structure similar to what is used in MAUI. Therefor it's nothing new and will be awesome for everyone that is not just interested in Blazor or ASP.NET.

image

In the example above I can use Blazor server for web first development (fast/hot reload advantage) but can now also extend my Blazor app to all platforms still within the same project structure. (with MAUI Blazor.)

Less confusion in my opinion and super awesome for any cross platform Blazor.

So simplistically I can reduce this then to only the following if you are not interested in the MAUI part. You now have a folder and not just a file to throw in all the parts that should be only applicable to that platform. So in essence any multi-targeting project should follow the MAUI implementation for consistency in the .NET ecosystem.

image

Running 6 platforms in one project structure will be my dream ;))

If I had to pick one of your options it would be option C. I pick option C because that would be the easiest way to integrate into a cross platform scenario as expressed above with my example.

Multi-targeting Blazor would be super awesome for sure !!

danroth27 commented 1 year ago

would approach C also make it possible to reference more than one client project from the server project and then use them on different pages?

@SteveSandersonMS Thoughts on this?

Romfos commented 1 year ago

From my point variant #3 is better: 1) I have clear understanding - what code is working on backend, what on client 2) I have clear understanding - what are my dependencies on both sides 3) Better from security poitn of view - I have guaranties that no database credentials, e.t.c will be pass to client side. It just harder to use in a wrong way

Variants #1-2 works only for helloworlds, but for real world app are not so good. At the same time I agree that on the demo on dotnetconf it will be look "nicer" =)

on more note:

Benefits of approach C ("two projects")
No need for multitargeting

if i right understand it still will be working on both sides and second project is still multitargted for net8.0 and net8-browser otherwise it cannot be referenced by server project

Even if you decided to use option make sense to make it consistent with MAUI:

/Platform/Server/Program.cs 
/Platform/Browser/Program.cs
/Program.cs
/Components
/Views

paltfrom specific code in "/Platform/" all other code - shared Maybe make sense to make it default for any project\app type for multitargeting and do not reinvent the wheel every time?

khalidabuhakmeh commented 1 year ago

I agree with the folks on option C as well. The boundaries are much more clear in that case.

netty2019 commented 1 year ago

I choose A, the structure is more concise, and it is more suitable for project transformation of server mode

seclerp commented 1 year ago

I vote for option C because mixing client and server in one project will end up with an absolutely confusing mess, making the initial concerns even more critical.

antomys commented 1 year ago

Personally, i would choose option c as-well.

xairaven commented 1 year ago

I vote for option C. Totally agree with @seclerp

DjeeBay commented 1 year ago

Option C is definitely more convenient for web developers. It's a good thing to be able to clearly identify which is backend (Server) and which is frontend (Client).

Then developers need to choose if the server is just an API consumed by client(s), renders pages/components or can do both. Sample templates are great to show the different possibilities.

Finally I think it could be useful to keep a Shared project as it clearly shows one of the biggest strength of Blazor : You can use classes and functionalities between backend and frontend and that is mind blowing when you come from other technologies. (I speak as a PHP/JS developer and I instantly felt how productive I could be with that).

One don't need a project to rule them all with a lot of magic. One need a solution with a clear separation of concerns.

qt-kaneko commented 1 year ago

I vote for C

UltWolf commented 1 year ago

I would vote for Option C. It is the simplest path in my opinion.

gerneio commented 1 year ago

Option C is def most likely to be the end product for a mature project (IMO), but I would say we probably need to evaluate why with the transition from Xamarin.Forms to MAUI we decided to transition from separate OS project structure to the folder structure as it is w/ MAUI today. By identifying what were the main reasons and advantages of each, perhaps that will help us implement the correct structure out of the gate. I personally have not used either extensively, so I'm not sure what the obvious pitfalls are to be had for each type of structure, but I think by looking at past decisions (and why they were made) it will help sculp the future. I think sometimes consistency across a stack is the best course of action (i.e. such as how we standardized program.cs/startup for the most part), but I know that it's getting harder as .NET reaches more platforms and crosses over into more domains. In the end, it might be best to just let the developer decide and give them a bridge for implementing their desired structure, so the decision might mostly come down to how do we setup the initial project templates, which would obviously influence the default usage for most users.

Just my two cents!

mohiyo commented 1 year ago

I lean towards option C but I have some very basic questions.

  1. Which Project will be set as default to Run? Client I Suppose
  2. How about Routing (is there any common configuration for Server/Client)
  3. Can we Share Components b/w Server and Client Projects in Option C?
marinasundstrom commented 1 year ago

@mohiydoeen

  1. The Server project will be the entry point, since it is hosting the WebAssembly project containing the WASM app.
  2. I don’t know whether you can host a selected page entirely in WebAssembly. The new model is SSR by default and opt-in to Server or WebAssembly at a component-level.
  3. Sharing components will not be a problem as long as dependencies are correct. Blazor will detect if a nested component has render mode Server or WebAssembly.

But in the case of C the Client WASM project will be explicitly in its own project. With no magic involved.

Otherwise, C is like a hosted WASM project but with the Server referencing the Blazor WebAssembly project. Allowing you to reference the WebAssembly project components from the server.

The unified Blazor app model will deal with transitioning between the modes. Rendering a component in the appropriate mode, even if nested.

jakubmaguza commented 1 year ago

While I agree with most that option C looks best, because we have clear boundaries, I'm not 100 sure it's right choice (which means maybe it is right!)

I remember few @SteveSandersonMS talks, where you mentioned, that maybe later it would be possible to write an app like it is server-only, using all your DBContexts directly, and then when it will be used like WASM, there will be a smart translation of your direct database call to some sort of http call. Compiler, or source generator, or whatever will create API layer for those direct calls.

If this could be possible with C, i will go for C. Otherwise I will stick with A. I believe we should by default exclude everything from client.

wanderlust-li commented 1 year ago

I vote for C

rogihee commented 1 year ago

I vote for C, it's clearer and with any normal project you soon grow out of 1 single project. In our setup, I treat Client Apps and Web hosts as merely empty shells, that are orchestrators for various RCL's. The RCL's contain all components and functionality, including the page directives. Depending on where the components are used, an App includes assembly in AdditionalAssemblies or not. And uses different DI implementations for API call services, etc etc. Right now, only a few are statically rendered with the tag helper in server side pages, but that is soon to changed for a lot more :-).

I have a need for multiple Client Apps connected to 1 host though. It would not be a problem for me to restrict it to 1 per page, so routing is sort of managaeble.

carlosalcantara2668 commented 1 year ago

For me Option C is the best of all the others. because it allows you to scale applications in a simple and robust way

XomegaNet commented 1 year ago

I would also vote for the option C, just like the majority of those who commented here so far.

Our solution template uses a *.Blazor.Common project for the components and classes that are shared between the *.Blazor.Server and the *.Blazor.Wasm projects, as described here.

We also put the common client logic and the business services in their own projects. The services have access to the DB and can be easily exposed via various APIs, such as REST, gRPC or even WCF. This allows reusing the same code not only between the Blazor Server and Web Assembly projects, but also with other .NET-based technology stacks - from the legacy Web Forms and WPF to the newer MAUI projects.

You can check out how it all works in this architecture diagram.

achmstein commented 1 year ago

I vote for Option C.

citizenmatt commented 1 year ago

I just want to make it very clear that #if blocks are actively hostile to automated IDE coding tools such as Rider, ReSharper and Roslyn. They should not be suggested as a legitimate way to write maintainable code, and any solution that relies on them should not be considered as a serious option.

dmitry-pavlov commented 1 year ago

I vote for option C as I prefer to keep client and server things clearly separated. If I build Blazor Server - it's clear, when I build Blazor WebAssembly - I usually have server side API project with generated API clients used to access data on WASM side (no UI pages / components on server side in this case).

IMHO: conditional directives in the source code suck.

oluatte commented 1 year ago

@SteveSandersonMS How would razor class libraries work in these scenarios? Will we need two RCLs one for the server and one for the client only?

KristofferStrube commented 1 year ago

I'm interested in how this will play together with using Razor Class Libraries targeting both Blazor Server and Wasm and how we can minimize the work needed for a library consumer when adding a third-party library to their project. What I most often see is the need to add some service that is injected into each individual page or component through the DI container to add some extra functionality. My impression is that a lot of vendors of libraries already have functionality in place that makes it possible to call the same function to add the needed services and potentially make configurations automatically depending on whether the project is Blazor Server or Wasm.

So having two separate Program.*.cs files that would both need to add multiple services in the exact same way seems like an extra step to have to explain to consumers of libraries. My guess is that most will probably just end up adding some extension method to the project (or just the Client project if solution C is picked) which adds the shared services and call that in both Program.*.cs files but it seems a bit clunky having some extension method called .AddCommonServices(this IServiceCollection serviceCollection) as that doesn't explain what is added or why these are grouped together apart from being needed both places. AddUIServices might be a better name for this method.

I'm not saying that we need to have a good solution for this just that it seems a bit cumbersome. I haven't used MAUI so it might be interesting to see how they manage to have services set up while still targeting different platforms with different needs/capabilities without extra extension methods or code duplication.

SteveSandersonMS commented 1 year ago

@mayoatte How would razor class libraries work in these scenarios? @KristofferStrube I'm interested in how this will play together with using Razor Class Libraries targeting both Blazor Server and Wasm and how we can minimize the work needed for a library consumer when adding a third-party library to their project.

If there was a single-project option (A or B) then it would be multitargeted over net8.0 and net8.0-browser. Class library authors could either target net8.0 and then write code that works in both environments, or they could also multitarget the class library over net8.0 and net8.0-browser and then use conditional compilation to put in different implementations or functionalities for the two platforms.

SteveSandersonMS commented 1 year ago

Thanks everyone for all the feedback here! Clearly there's a strong community consensus, which is that most people prefer the two-project approach (option C). We're keen to listen, and so the two-project approach is how we'll structure the new default Blazor Web template. We recognize its advantages in terms of being more familiar and standard, with less need for new compilation rules or conventions.

A couple of points just to ensure everything is fully communicated:

  1. This means the new unified Blazor architecture will look slightly different to what we showed in demos earlier this year. For example, in a video about the prototype, we had a single project and toggled components between SSR, Server, and WebAssembly modes just by setting a @rendermode, with no other changes needed. In the two-project system, making a component run on WebAssembly will also involve moving it to the WebAssembly project (the .Client project in the diagram above) if it's not already there. Hopefully that's not a big problem for anyone.

  2. We are still open to experimenting with single-project approaches in the future. For example, we might make an unofficial, experimental template package to let people try this out if they want (as part of other experiments). But to be clear, we hear the overwhelming consensus is that people want a multiproject approach and so that is the default and in fact will be the only WebAssembly-enabled option for .NET 8.

I hope that sounds right and makes sense. This approach (C) will now appear in an upcoming preview release, perhaps preview 7 (note: not preview 6, as that's already locked down).

Pinox commented 1 year ago

Thanks Steve. I think you might have had a different result or at least more balanced vote if you included a pure "MAUI based" multi-targeting approach but that was not on the table here. I for one would have preferred that over options C (although I voted option C) as Diagram A + B is much more confusing than a "MAUI multi-targeting" approach. So the result you got here is more reflective of the suggested designs.