Open markphillips100 opened 4 years ago
In my mind the ideal result would be something like:
[HttpGet("/sample/{id}")]
public Task Handle(HttpRequest request)
{
var vm = request.GetComposedResponseModel<MyStonglyTypedModel>();
//use the strongly typed model
return Task.CompletedTask;
}
This could be enabled by injecting something like:
interface IViewModelFactory
{
object CreateViewModel(HttpRequest request);
}
class MyStonglyTypedModelFactory : IViewModelFactory
{
[HttpGet("/sample/{id}")]
public object CreateViewModel(HttpRequest request)
{
return new MyStonglyTypedModel();
}
}
There are many more things that need to be changed to enable the above, as of now the dynamic view model does too many things that need to be moved to a CompositionContext kind of thing to avoid forcing your model to implement an interface and thus violate SRP.
If we can achieve the above another step could be:
//this comes from assembly provided by sales
interface ISalesSampleViewModel
{
decimal Price{ get; set; }
}
//this comes from assembly provided by marketing
interface IMarketingSampleViewModel
{
string Description{ get; set; }
}
//this is defined in a branding owned assembly
class MyStonglyTypedModel : ISalesSampleViewModel, IMarketingSampleViewModel
{
//properties here
}
[HttpGet("/sample/{id}")]
public Task Handle(HttpRequest request)
{
var vm = request.GetComposedResponseModel<ISalesSampleViewModel>();
//the sales composition handler knows nothing about the marketing side
return Task.CompletedTask;
}
@markphillips100 I made (finally) significant progress on this. https://github.com/ServiceComposer/ServiceComposer.AspNetCore/pull/155 Is the first step toward being able to plug-in a completely custom, and strongly typed, view model.
For backward compatibility reasons as of now the only and experimental way to plug-in a custom model is by using interfaces only. This is enabled by a new package I’m working on https://github.com/ServiceComposer/ServiceComposer.AspNetCore.TypedViewModel/pull/2
with the new package installed composition handlers looks like the following:
//this comes from assembly provided by sales
interface ISalesSampleViewModel
{
decimal Price{ get; set; }
}
//this comes from assembly provided by marketing
interface IMarketingSampleViewModel
{
string Description{ get; set; }
}
[HttpGet("/sample/{id}")]
[TypedViewModel(typeof(ISalesSampleViewModel))]
public Task Handle(HttpRequest request)
{
var vm = request.GetComposedResponseModel<ISalesSampleViewModel>();
//the sales composition handler knows nothing about the marketing side
return Task.CompletedTask;
}
There is no need for a class the implements all the interfaces because that class is dynamically generated at runtime thanks to the TypedViewModel
attribute applied to composition handlers.
the same attribute is used by the API generation to provide proper documentation:
you can have a preliminary look at how it works in #3
there is still a lot of work to do before being able to use it in production, I’m confident that this is a promising direction.
I'll take a look as soon as I can @mauroservienti but I really appreciate your efforts here.
My first thoughts are whether this approach would also cater for lists of objects where each object is composed from more than one service, via handler and then subscribers as usual? This is the typical "list of orders" use case where each order has some sales data, some finance data, etc.
My first thoughts are whether this approach would also cater for lists of objects where each object is composed from more than one service, via handler and then subscribers as usual? This is the typical "list of orders" use case where each order has some sales data, some finance data, etc.
This experiment doesn't support complex object graphs (yet, maybe). It's incredibly complex to support whatever the consumer expects to do, especially if the expectation is "whatever C# allows" 😅
I'm working on 1.8.0 that comes with custom view models factories (implementation merged in https://github.com/ServiceComposer/ServiceComposer.AspNetCore/pull/155). Next step is documentation and a couple more things (https://github.com/ServiceComposer/ServiceComposer.AspNetCore/milestone/1). Once 1.8.0 is good to go it can be used to provide support for Swagger and friends.
I'm leaning towards thinking a strong type model should be used if documentation is required for response types, i.e. try to use as much of the existing MVC metadata modelling as possible. I especially like the idea that other annotation attributes can be leveraged then; DisplayName etc.
If strong type composition can be achieved by https://github.com/ServiceComposer/ServiceComposer.AspNetCore/pull/155 then great.
Is the intention to support setting a viewmodel dynamic property to a given strong-typed value? Like this:
Is it possible to merge lists of strong types too, in cases where a request subscriber wants to add their data to each record?