My .NET 6 ASPNET MVC application has the usual controller, model and view scenario, with a separate service layer/interface for getting and saving data. The service is injected into the controller via it's constructor, which gets the data and via the service. The service is also injected into the model's constructor, but because the model also requires a constructor with no parameters (for model binding when the form in the view is posted), so the service reference within the model is null, which causes problems when a "computed" property of the model is accessed which relies on a call to the service.
I'm aware that I can access the service instance when validating the model (via IServiceProvider being implemented by the ValidationContext argument to my model's implementation of IValidatableObject.Validate) but how can I access an IServiceProvider elsewhere in a model that's been constructed using the default constructor?
I've thought about encapsulating an IServiceProvider within a global/singleton class but suspect that would cause issues and doesn't seem at all elegant.
CodePudding user response:
I presume you are trying to do something like this:
class MyModel
{
readonly MyService _myService;
public MyModel(MyService myService)
{
_myService = myService;
}
public string Prop1 { get; set; }
public string ComputedProp => _myService.Compute(Prop1);
}
A model for posting to a controller should really be a pure DTO (Data Transfer Object) and not include any behaviour such as this.
I would echo WiseGuy's suggestion and implement as follows:
class MyModel
{
public string Prop1 { get; set; }
// Initialize to avoid model binding validation error
// when null
public string Prop2 { get; set; } = string.Empty;
}
class MyController
{
readonly MyService _myService;
public MyController(MyService myService)
{
_myService = myService;
}
public async Task PostAsync(MyModel myModel)
{
myModel.Prop2 = _myService.Compute(myModel.Prop1);
// Rest of method
}
}
I would go further to say that a computed value does not belong on the DTO at all. Ideally the model used to post to your controller is not the same class as is used deeper in your application and the model should be transformed in the controller:
class MyModel
{
public string Prop1 { get; set; }
}
class MyEntity
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
class MyController
{
readonly MyService _myService;
public MyController(MyService myService)
{
_myService = myService;
}
public async Task PostAsync(MyModel myModel)
{
MyEntity myEntity = new MyEntity()
{
Prop1 = myModel.Prop1,
Prop2 = _myService.Compute(myModel.Prop1);
}
// Rest of method - using myEntity, not myModel
}
}