7

We are starting on a greenfield project in ASP.NET MVC. I've used the MVC pattern in other stacks (PHP and Ruby) and I keep running into the same problems of where to place business logic, UI logic, and where should this system interact with other remote systems (i.e. web services and e-mail).

I was reading Advantages of ViewModel in MVC and the author states that there are 5 kinds of "logic" in an application:

  • Business Logic – Is a part of Model

  • Database Logic – This is the one of the non-spoken layer in MVC. Many people create a separate layer for this and invoke them from Business layer (model) or some keep them inside Model.

  • User Interaction logic – written inside controller

  • Presentation Logic – Presentation logic is the logic which will handle the presentation of a UI. Example – If some value is greater than 100 it should be displayed in green color. Basically it is a part of View.

  • Data transformation logic – In some situations we have some data with us and we want to display it in a different format. Example we have Date-of-birth and we want to display Age. Again this transformation becomes the part of View.

I would add one additional "kind of logic" which is:

  • User Interface/Domain Model Manipulation Logic - How to transfer data from the view to data and method calls in the Domain Model

I believe the first 5 kinds of logic have defined areas already. Business Logic goes in the Domain Models. Database Logic goes in the Repositories or O/RM layer. User interaction logic goes in the controller. Presentation logic goes in the View Model. Data transformation logic goes in, essentially, classes that format data, such as dates and numbers.

The last kind of logic, where you must take data from the view and manipulate the Domain Model accordingly, doesn't seem to have its own place.

I've read about two possibilities:

  1. A "service layer" in the application, although no mention is made of Web Services, but using "service objects" to encapsulate this

  2. A "unit of work" object - This has been especially frustrating because a database transaction is an implementation of the Unit of Work Pattern. Recent variations of this pattern augment the database transaction with the actual manipulations of the Domain Model.

  3. An Action/Executor object (Action/Executor Pattern)

  4. Command objects (Command Pattern)

Creating a new blog post is easy - insert the post into the database. Creating a new user is not so straight forward, because you might want to send a welcome e-mail after inserting the user into the database, so additional operations are performed after the "unit of work" is complete.


A small code example.

Let's say we have a blog application.

  1. A blog has many posts
  2. A blog post cannot exist without a blog
  3. A blog post must have a title, body and publish date

This leads to two Domain Classes: Blog and BlogPost. To prevent you from accidentally creating a BlogPost object and adding it to the incorrect Blog, the constructor for the BlogPost class is marked internal so you are forced to add a blog post through the Blog object to which it belongs:

public class Blog
{
    public Blog(string name)
    {
        Name = name;
        posts = new List<BlogPost>();
    }

    public int Id { get; internal set; }
    public string Name { get; private set; }
    public string Description { get; set; }

    private IList<BlogPost> posts;

    // Return a read-only collection of posts to the public
    public IEnumerable<BlogPost> Posts
    {
        get { return posts; }
    }

    // Create a new BlogPost object to ensure it is attached to the proper Blog
    public BlogPost AddBlogPost(string title, string body, DateTime publishDate)
    {
        BlogPost post = new BlogPost(this, title, body, publishDate);

        posts.Add(post);

        return post;
    }
}

And the BlogPost class:

public class BlogPost
{
    // This constructor is "internal" so code outside this Assembly must call
    // Blog.AddBlogPost(...) to create new posts
    internal BlogPost(Blog blog, string title, string body, DateTime publishDate)
    {
        Blog = blog;
        SetTitleAndBody(title, body);
        PublishDate = publishDate;
    }

    public int Id { get; internal set; }
    public string Title { get; private set; }
    public string Body { get; private set; }
    public Blog Blog { get; private set; }
    public DateTime PublishDate { get; private set; }

    public int BlogId
    {
        get { return Blog.Id; }
    }

    public bool IsPublished
    {
        get { return PublishDate < DateTime.Now; }
    }

    public bool IsUnpublished
    {
        get { return !IsPublished; }
    }

    public void PublishAt(DateTime newPublishDate)
    {
        PublishDate = publishDate;
    }

    public void SetTitleAndBody(string title, string body)
    {
        if (string.IsNullOrEmpty(title))
            throw new ArgumentNullException("title");

        if (string.IsNullOrEmpty(body))
            throw new ArgumentNullException("body");

        Title = title;
        Body = body;
    }
}

The View Model for a Blog Post might look like:

public class BlogPostForm
{
    public BlogPostForm()
    {
    }

    public BlogPostForm(BlogPost post)
    {
        Id = post.Id;
        Title = post.Title;
        Body = post.Body;
        PublishDate = post.PublishDate;
        BlogId = post.BlogId;
    }

    [Required]
    public int Id { get; set; }

    [Required]
    public string Title { get; set; }

    [Required]
    public string Body { get; set; }

    [Required]
    public DateTime? PublishDate { get; set; }

    [Required]
    public int BlogId { get; set; }
}

Note that the PublishDate is a nullable DateTime because the user might submit the form without filling out the publish date. Furthermore all properties are public get/set, unlike the Domain Model, again because the user might submit the form without filling in those fields.

Now something needs to take the data in the BlogPostForm object and find the Blog in the database, and call AddBlogPost:

IBlogRepository blogs = ...

Blog blog = blogs.Find(model.BlogId);
BlogPost post = blog.AddBlogPost(model.Title, model.Body, model.PublishDate.Value);

blogs.Save(blog);

Where should we put the logic of how to manipulate the Blog and BlogPost objects based on values in the BlogPostForm object, especially if this work includes more than a COMMIT to a database?

2
  • There are lots of ways to do this, of course it depends... it depends on numerous factors like: a) how complex your business logic is? b) how do you users do their "work" ? As in, is your Web UI just plain forms-over-data, or is it (or could it be) some kinda task based UI where you can separate actions (queries/commands) ? Also it could be some ERP style active-recordish madness, where the business logic (and the UI) might have cross validations using multiple level master-detail data and offering values to a set of other objects/levels etc. I understand the blog post example is easy to...
    – kayess
    Commented Jun 1, 2017 at 8:43
  • ... illustrate your question/problem space, however to get a concise answer, I think you should address such concerns.
    – kayess
    Commented Jun 1, 2017 at 8:43

4 Answers 4

9

Bear in mind that the Model View Controller (MVC) principal has been hijacked and bastardized by the web development world. In a real MVC application the model view and controller are largely orthogonal. If you insert a record into the model, the view will receive a change event, possibly with a hint as to what part of the model has changed and then update only the part of the view that is affected. This is not applicable to the web because the web is stateless so the "view" is completely reconstructed with each action. Sometimes AJAX can help with this but generally, the view cannot react to changes in the model. So don't break your neck trying to reconcile the semantics of MVC with respect to web applications. The web stack is actually pretty horrible. You are not going to find elegant design patterns in this stuff. You just have to try different things and be creative. Don't commit yourself to some design because a lot of people are preaching a particular method. I've done a lot of different kinds of programming from DSP chips to network security software and web development is probably the most difficult of all not just because the web stack sucks but also because anyting that interfaces with a human being is going to be highly subjective.

2
  • 2
    Ugh. +1 not because you solved the problem I presented, but because someone finally admits that programming for a disconnected, stateless interface... well... sucks. Commented Mar 4, 2016 at 13:34
  • I'd say that javascript has helped us get a lot of state into webpages. I haven't actually made an application using a javascript MVC framework but I'd definitely be interested in evaluating the 'stateful' web Commented Jul 18, 2016 at 21:10
1

It is the job of the Controller in MVC to take the data that the user has entered and to modify the Model accordingly.

In many cases, this might be as simple as hooking up a class from the Model to the View (or ViewModel) and caliing Commit on the database after the user confirmed the edits.
But sometimes, like in your example with the BlogPost, the logic in the Controller has to be more involved to ensure that the constraints laid down by the Model are observed.

8
  • I'm not talking about user interaction logic. This is more like, to create a BlogPost object, a title and body is required, so the title and body are required arguments to the constructor: new BlogPost(model.Title, model.Body) - some class in the system must know and properly execute this. Commented Mar 3, 2016 at 19:12
  • @GregBurghardt: that looks more like validation of the user input, which is also a kind of User Interaction Logic. Commented Mar 3, 2016 at 19:23
  • I've updated my question to better illustrate the problem I'm trying to solve. Commented Mar 3, 2016 at 19:34
  • I agree that validation is user interaction logic, but the reason something is invalid is business logic, and therefore goes in the Domain Model. Furthermore, the Domain Model should utilize encapsulation so that the data in the Domain Model is not invalid to begin with. Commented Mar 3, 2016 at 19:36
  • Rails has model validations, is that the kind of thing you're looking for?
    – Andrew
    Commented Mar 3, 2016 at 19:38
1

If we are strict in the pattern, what you are asking should be handled by the controller, simply because it creates a bridge between the view and the model.

But, of course, you won't have all your logic in the controller. Usually a Service Layer is used for this. And a SL isn't but another way to call the exposed methods of the business layer, like say, if you have a BlogPostService class, with a method Post(), that method could be something like this:

var blog = blogRepository.Get(post.blogId);
postRepository.Post(blog,post);
tagRepository.Add(tags,post);
notifierService.Notify(post);
blogService.SendtoFrontPage(post);

From your UI you do need to worry about transforming the objects, like say:

IConverter<viewModelBlogPost,ModelPost> _converter;
IBlogPostService _blogPostService;

var modelPost = _converter.Convert(viewModelBlogPost);
_blogPostService.Post(modelPost);

Your converter logic needs to be in the presentation because it has to know about the view model objects.

0

Where should we put the logic of how to manipulate the Blog and BlogPost objects based on values in the BlogPostForm object, especially if this work includes more than a COMMIT to a database?

Manipulate them how?

You messing with the title? The body? The publish date? The ID?!?!

Blogs have business rules that dictate who can set what. Databases have rules about how you can change records. Forms have rules ... well no forms are a mess you have to figure out how to make sense of without letting users drop all your DB tables.

All of these are separate responsibilities that deserve their own place to live.

User Interface/Domain Model Manipulation Logic - How to transfer data from the view to data and method calls in the Domain Model

This is a controller. Remember a controller isn't the code in the UI. It's where messages from any input (UI or not) go to be processed before the model is changed at all.

Some people make anemic controllers because they don't fully understand the roll they play leaving us with bloated UI code trying to make up for it. UI's should barely have code in them at all.

programming for a disconnected, stateless interface... well... sucks

Changing an UI that knows who the valid users are also sucks.

UI's shouldn't ask questions. They should tell what they want done and they should be told what to show.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.