Reputation: 9490
I'm in the middle of re-factoring the project I'm working on. In my existing controllers I do use the repository pattern, but I was still performing a little too much scaffolding than I felt comfortable with. That and some of my controllers could have 10+ repositories passed in (via Ninject). So, I decided to introduce a service layer where my intention is to have one service per controller and each service will instead have the multiple repositories injected into it and do the work I need. This works great so far, but I'm running into a confusion of sorts: How do I move the model validation away from the controller and into the service layer?
For example, take a look this Edit
method on my OfficesController
:
[HttpPost]
public async Task<RedirectToRouteResult> Edit(
short id,
FormCollection form,
[Bind(Prefix = "Office.Coordinates", Include = "Latitude,Longitude")] Coordinate[] coordinates) {
if (id > 0) {
Office office = await this.OfficesService.GetOfficeAsync(id);
if ((office != null)
&& base.TryUpdateModel(office, "Office", new string[2] {
"Name",
"RegionId"
}, form)
&& base.ModelState.IsValid) {
this.OfficesService.UpdateOfficeAsync(office, coordinates);
}
return base.RedirectToAction("Edit", new {
id = id
});
}
return base.RedirectToAction("Default");
}
The problem with it in comparison to the methods of the controller is that I still grab an Office
object from the database, do the update, validate it, and then save it again. In this case the complexity increased rather than decrease. Before, I called the repository in the method, now I call the service which calls the repository to perform the same function. So far this increase in complexity has only show it self in my Edit
methods, everywhere else the complexity decreased substantially, which is what I want.
So, what would be a proper way move validation, and now that I think about it, the model updating logic out of the controller and into the service? Recommendations are appreciated!
For reference, here's how my project is structured:
FindTechnicians()
or FindActive()
, etc.Upvotes: 3
Views: 2154
Reputation: 47375
Here are 2 articles you should read if you haven't already:
The answers to your problem are FluentValidation.NET and dependency decoration.
With it, you could do something like this:
private readonly IExecuteCommands _commands;
[HttpPost]
public async Task<RedirectToRouteResult> Edit(short id, UpdateOffice command) {
// with FV.NET plugged in, if your command validator fails,
// ModelState will already be invalid
if (!ModelState.IsValid) return View(command);
await _commands.Execute(command);
return RedirectToAction(orWhateverYouDoAfterSuccess);
}
The command is just a plain DTO, like a viewmodel. Might look something like this:
public class UpdateOffice
{
public int OfficeId { get; set; }
public int RegionId { get; set; }
public string Name { get; set; }
}
... and the magic validator:
public class ValidateUpdateOfficeCommand : AbstractValidator<UpdateOffice>
{
public ValidateUpdateOfficeCommand(DbContext dbContext)
{
RuleFor(x => x.OfficeId)
.MustFindOfficeById(dbContext);
RuleFor(x => x.RegionId)
.MustFindRegionById(dbContext);
RuleFor(x => x.Name)
.NotEmpty()
.Length(1, 200)
.MustBeUniqueOfficeName(dbContext, x => x.OfficeId);
}
}
Each of these validation rules will be run before your action method even gets executed, provided you have the validators set up for dependency injection and that you are using the FV MVC validation provider. If there is a validation error, ModelState.IsValid will be false.
You have also just solved the over injection problems in both your controller and (maybe) service layers. You can run any query, execute any command, or validate any object with only 3 interface dependencies.
Upvotes: 2