MarimerLLC / cslaforum

Discussion forum for CSLA .NET
https://cslanet.com
Other
31 stars 6 forks source link

MVVM and Blazor #891

Open Chicagoan2016 opened 4 years ago

Chicagoan2016 commented 4 years ago

Hi folks, so I am reading Blazor book and there is my beloved MVVM : ). every time I encounter MVVM in .NET I ask myself; could I use Prism here? Do you need a MVVM 'framework' in Blazor or it's just making life complicated?

Kind regards

Version and Platform CSLA version: 4.7.100 OS: Windows, Linux, iOS, Android, etc. Platform: WinForms, WPF, ASP.NET Core, MVC, Xamarin, etc.

rockfordlhotka commented 4 years ago

I think the answer is probably yes.

As @thecakemonster pointed out, my ProjectTracker implementation isn't ideal, because there's still code in the pages. The ViewModel class in Csla.Blazor supports moving nearly all the code out of the page, but then you can't use DI to inject it, because you'll have a custom viewmodel subclass per page.

The thing is, to get unit testing for your viewmodel, your viewmodel can use NO TYPES from the UI framework, or if they are used, those types must be something you can mock. I'd think you'd need to abstract the same basic UI concepts as I abstracted in the Bxf framework years ago - that'd be the minimum necessary "MVVM framework".

Abstract at least:

  1. Displaying app and page-level error messages
  2. Wiring up view-viewmodel-model instances during navigation
  3. Navigation to another view

Now Blazor's types might be mockable or interface-based - I haven't looked. For example, NavigationManager is the Blazor type used to navigate - and you'd need to inject a test version of that type for unit testing, you can't use the real type.

I do think most big-name MVVM frameworks are far more than just MVVM - they are entire UI frameworks that extend/enhance a Microsoft UI platform. I am not sure that's necessary with Blazor, because the Blazor UI platform/framework is really well designed and enables the sorts of composition I'd look for it a helper framework for WPF, etc.

JasonBock commented 4 years ago

There are three things that are automatically injectible in Blazor: HttpClient, IJSRuntime, and NavigationManager (https://docs.microsoft.com/en-us/aspnet/core/blazor/dependency-injection). NavigationManager is abstract, but NavigateTo() is not virtual. So it's unclear at a glance how easy/hard it would be to have a testable VM that injects NavigationManager (maybe LocationChanged is raised when NavigateTo() is called? I may need to do a blog article on this... :) )

Chicagoan2016 commented 4 years ago

Thanks @rockfordlhotka and @JasonBock , we have looked at Prism and MVVMLight in the past for WPF. My personal preference was Prism (I was/am a fan of Patterns and Practices group). It appears Prism has support for WPF and Xamarin but haven't found anything concrete about Blazor yet. Rocky's statement

I do think most big-name MVVM frameworks are far more than just MVVM - they are entire UI frameworks that extend/enhance a Microsoft UI platform. I am not sure that's necessary with Blazor, because the Blazor UI platform/framework is really well designed and enables the sorts of composition I'd look for it a helper framework for WPF, etc.

does sound like what we found, MVVM frameworks especially Prism cover alot of ground.

TheCakeMonster commented 4 years ago

A very interesting - and timely - discussion. We start work on a new web application using Blazor server-side and CSLA in about a week's time, and I'm just at the point where I am deciding the architectural patterns we should follow.

I've been sold on MVVM despite initial concerns; once I'd appreciated that Blazor is stateful, it suddenly made sense to instantiate ViewModels, something which seemed solely to work the garbage collector harder in request/response scenarios of older web technologies.

What surprised me about the ProjectTracker sample was that there was more than minimal code in the Razor components. There's not a lot, but as components/pages get more complex, I can see this growing.

Our first web application using Blazor (without CSLA, in that particular instance) definitely benefited from moving the logic into a separate class - the ViewModel. That class became more complex as we understood all of the possible outcomes of the screens, and the behaviour and logic they required. The benefit was not at app runtime, but the testability it provided. The logic was a bit more complex than we initially expected, and unit testing became more beneficial as a result. Unit testing of Razor components will be possible in future - a sample of doing so is already available - but it seemed that making the logic testable without a complex framework was easy if the code was not in the UI components.

I wonder if there might be two things under discussion here: reusability of ViewModels across multiple UIs, and the benefit of separating logic from presentation even if ViewModels are not reusable across different UIs. I think I was thinking about the latter, whereas I think @rockfordlhotka might have been aiming for the former - and and far nobler - cause.

What I think Rocky is saying makes sense of course - best of all is to create ViewModels that could be reusable between different UI technologies. That requires abstraction of some of the concepts. However, even if that isn't achieved in the first instance, I feel that moving logic out of the UI layer has a benefit, in that it aids easier testability of that logic.

I'm interested in the suggestion that custom ViewModels per Razor Component wouldn't be compatible with DI. As far as I understand it, anything that is registered can be injected - but maybe that was the point? Every ViewModel would need to be registered with DI if there is a custom one per 'page' - and I understand that's not ideal. In the fullness of time, there may be a way to use convention over configuration in conjunction with reflection to do the registration semi-automatically; in the meantime, maybe registering each type separately might be good enough in some scenarios?

My head is currently on custom ViewModels per scenario/user story/whatever, but dedicated - indeed tied entirely - to Blazor/Razor components. I hadn't even thought of abstracting navigation, as in my situation, I don't think this is necessary. In our situation, I see a ViewModel as part of each individual application/UI, but that separating the logic they contain from the components themselves is still beneficial, as it makes testing the logic within them easier as they become more complex.

Your mileage may vary!

TheCakeMonster commented 4 years ago

Hmm, OK, after re-reading the text, maybe I get the point. What Rocky was saying was that use of any type that can't be mocked in the ViewModel breaks the idea of testing it, because it can't be run outside of the UI.

NavigationManager is the one class that stands out as being a problem in this case. This used to be injected by requesting an interface rather than a type, but changed late on in the previews to being a type rather than that interface. Even so, I think it's still testable - but I worry that maybe I've missed a small implementation detail, and I'm wrong.

I was surprised that this change from interface to type happened when it did, so late in the Blazor previews. I didn't quite understand why this happened at the time, but in hindsight maybe this is to do with static members.

I think NavigationManager has different implementations for client-side and server-side scenarios, so there is still some ability to replace the implementation, but maybe it's no longer enough to achieve testability.

I'll have to go back to the implementation of our first site and check whether the developers actually wrote any unit tests. I have to confess, I assumed they would have done - but maybe they never did.

Chicagoan2016 commented 4 years ago

@TheCakeMonster , a couple of questions 1) Why server-side Blazor for new project ? I know WebAssembly is in preview but MS is going to release it in May. 2) Do you plan to look at any of the MVVM frameworks, especially Prism?

Regards

TheCakeMonster commented 4 years ago

@Chicagoan2016 we're using server-side partially because WebAssembly isn't fully supported yet, but that's not the only reason. Our codebase is currently quite integrated, as a result of which a lot of our IP would be downloaded to the client with a WebAssembly Blazor application. It's going to take me some time to unhook the dependencies to reduce the need for all of the back-end code to be downloaded; in the meantime, using server-side gives us a head start on using Blazor.

It's not an ideal situation, but needs must.

I hadn't thought of looking into MVVM frameworks. The code I had seen to date had given me the impression that Blazor was sufficiently complete for our needs. However, I am now wondering if that might be a little naive on my part.

JasonBock commented 4 years ago

@TheCakeMonster I just finished my investigations around writing tests for code that uses NavigationManager. It's.....interesting. I'm definitely writing an article on this!

Chicagoan2016 commented 4 years ago

@JasonBock , I have to ask, do you use any of the 'commercial' frameworks like Typemock?

JasonBock commented 4 years ago

@Chicagoan2016 nope. I either use Moq or my own :) (https://www.github.com/JasonBock/Rocks). I try to stay away from testing that hooks the Profiler API so you can mock static methods, non-virtual members, and so on. These tests end up being fragile and it's better to use other techniques (like decorator patterns) to "own" those dependencies.

Chicagoan2016 commented 4 years ago

I am looking at Rocks, so glad you have a 'Quickstart' page : )

JasonBock commented 4 years ago

Just finished the article. Hopefully it'll get published soon.

Chicagoan2016 commented 4 years ago

@JasonBock , where will the article get published? Magenic website?

JasonBock commented 4 years ago

It's being reviewed right now. It'll show up on Magenic's site once it's accepted.

TheCakeMonster commented 4 years ago

Thanks @JasonBock, that'll be really useful. I'll look out for that.