Lets say I wanted to make a post request to update the status of a house, ideally this data should be in some sort of service layer, typically this involves
- validate user - are they still active or been kicked out by admin?
- check the houseid - is the houseid/ record valid?
- can the user see the house details?
- update status to "open" or "closed"
In a real world/ complex domain - most views are very complex, we have to throw out maybe the number of houses in the area, how many comments on the house, the house details so on, maybe the number of outstanding tasks on the house...
In short - all the above code may be inside a service layer, however lets say an exception is thrown, a user cannot update the status on the house - now to populate the view you have to firstly get the house details (again), load up all other stuff that you just loaded up inside the service layer ALL of this inside the controller or another incovation to a service layer that loads up this data...
How can I ensure my domain model is protected by running validation and all the sorts without having to rewrite the same code multiple times...
This code is inside the action method, it could easily be inside a service layer...
//NOTE: _repo is a simple abstraction over linq to sql...
[HttpGet]
public ActionResult TaskDetail(int houseid, int taskid)
{
var loggedonuser = _repo.GetCurrentUser();
var _house = _repo.Single<House>(x => x.HouseID == houseid && x.Handler == loggedonuser.CompanyID);
if (_house == null)
throw new NoAccessException();
var summary = _house.ToSummaryDTO();
var companies = _repo.All<Company>();
var users = _repo.All<User>();
var task = _repo.Single<HouseTask>
(x => x.HouseID == _house.HouseID && x.TaskID == taskid && (x.CompanyID == loggedonuser.CompanyID));
var dto = new TaskDTO
{
TaskID = task.TaskID,
Title = task.Title,
Description = task.Description,
DateCreated = task.DateCreated,
IsClosed = task.IsClosed,
CompanyID = companies.Where(y => task.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
};
if (task.DueDate.HasValue)
dto.DueDate = task.DueDate.Value;
var comments = _repo.All<HouseTaskComment>()
.Where(x => x.TaskID == task.TaskID)
.OrderByDescending(x => x.Timestamp)
.Select(x => new TaskCommentDTO
{
Comment = x.Comment,
Timestamp = x.Timestamp,
CompanyID = companies.Where(y => x.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
UserID = users.Where(y => x.UserID == y.UserID).SingleOrDefault().Login,
Type = EnumHelper.Convert<TaskCommentType>(x.Type)
});
dto.AllComments = comments;
return View(new TaskViewModel
{
Summary = summary,
TaskDetail = dto,
NewComment = new TaskCommentDTO()
});
}
In short - get the house details for the summary, get the task detail (from the multiple available tasks) and also get the task comments. That is a simple view IMHO, nothing too complex.
At this point user can: Add comment, Close/ Open task - if they have permissions to do so (code was omitted for simplicity), set task due date, or even clear the due date for the task.
Now the UpdateTaskStatus - if it is not possible to update the status must return the above view, similar to comment, if you cant comment please return the detail view - comments may be closed.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TaskDueDate(int houseid, int taskid)
{
var duedate = Request.Form["duedate"];
var duetime = Request.Form["duetime"];
try
{
if (ModelState.IsValid)
{
var newduedate = DateHelper.GoodDate(duedate, duetime);
_service.SetTaskDueDate(houseid, taskid, newduedate);
return RedirectToAction("TaskDetail");
}
}
catch (RulesException ex)
{
ex.CopyTo(ModelState);
}
var loggedonuser = _repo.GetCurrentUser();
var _house = _repo.Single<House>(x => x.InstructionID == houseid && x.HandlerID == loggedonuser.CompanyID);
if (_house == null)
throw new NoAccessException();
var summary = _house.ToSummaryDTO();
var companies = _repo.All<Company>();
var users = _repo.All<User>();
var task = _repo.Single<HouseTask>
(x => x.InstructionID == _house.HouseID && x.CompanyID == loggedonuser.CompanyID && x.TaskID == taskid);
var dto = new TaskDTO
{
TaskID = task.TaskID,
Title = task.Title,
Description = task.Description,
DateCreated = task.DateCreated,
IsClosed = task.IsClosed,
CompanyID = companies.Where(y => task.CompanyID == y.CompanyID).SingleOrDefault().Identifier
};
if (task.DueDate.HasValue)
dto.DueDate = task.DueDate.Value;
var comments = _repo.All<HouseTaskComment>()
.Where(x => x.TaskID == task.TaskID)
.OrderByDescending(x => x.Timestamp)
.Select(x => new TaskCommentDTO
{
Comment = x.Comment,
Timestamp = x.Timestamp,
CompanyID = companies.Where(y => x.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
UserID = users.Where(y => x.UserID == y.UserID).SingleOrDefault().Login
});
dto.AllComments = comments;
return View("TaskDetail", new TaskViewModel
{
Summary = summary,
TaskDetail = dto,
NewComment = new TaskCommentDTO()
});
}
I know the code above is badly structured but some advice on how to rectify it will be appreciated.
- I am leaving all read only code inside the actions, because each view could be different, I dont want a service layer interfering here
- I want to "protect" my updates/ edits and hold this inside my service layer or core project (sepeparate c# class lib) or even Domain layer, how would I write that code- handle validation, (which is what I do inside the service call), perform the actual save?
I have heard of CommandHandler approach, is this a good approach? Ideally I want to hold my validating + persistance using a simple approach inside my domain not controller action....