Web applications - MVC style (Java & .NET)

Content:

  1. Introduction
  2. JSP + Servlet
  3. Struts 1.x
    1. Struts tags + form class
    2. JSTL tags + DynaForm
  4. Struts 2.x
  5. Stripes
    1. .action URL + JSTL tags
    2. UrlBinding + Stripes tags
  6. Spring MVC
    1. JSP view
      1. with JSTL tags
      2. with Spring tags
    2. Thymeleaf view
      1. with servlet container
      2. with Spring Boot
  7. Grails
    1. HTML form
    2. GSP form
  8. ASP.NET MVC
    1. ASPX view
    2. Razor view
  9. ASP.NET Core MVC

Introduction:

JSP + Servlet:

Strictly speaking this is not a web application framework as it is all custom code based on the basic building blocks of the technology.

But given that it is actually frequently used for MVC web applications, then it is relevant to cover.

Name and creator N/A
History Servlet 1.0 was introduced in 1996
JSP 1.1 was introduced in 1999
JSTL 1.0 was introduced in 2002
Programming language Java
View technology JSP
Deployment Servlet container
Other technical characteristics
Status All technologies involved are still actively maintained, but today it is probably only used for small projects - larger projects use a real framework

We have a servlet controller that forward to the JSP view.

JSP is covered in detail in Java EE tricks.

test.jsp (view):

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Java (JSP + Servlet)</title>
</head>
<body>
<h1>Java (JSP + Servlet)</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<c:forEach items="${data}" var="o">
<tr>
<td><c:out value="${o.f1}"/></td>
<td><c:out value="${o.f2}"/></td>
<td><c:out value="${o.f3}"/></td>
</tr>
</c:forEach>
</table>
<h2>Add data:</h2>
<form method="post">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
F3: <select name="f3">
<c:forEach items="${options}" var="opt">
<option><c:out value="${opt}"/></option>
</c:forEach>
</select>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

TestController.java (controller):

package demo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestController extends HttpServlet {
    // options
    private List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("");
        res.add(T1.VER);
        res.add(T1.NOTVER);
        return res;
    }
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // get all data and save in request for use by JSP
        request.setAttribute("data", DB.getAll());
        // get options and save in request for JSP
        request.setAttribute("options", getOptions());
        // forward to JSP
        request.getRequestDispatcher("test.jsp").forward(request, response);
    }
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // create object from form fields
        T1 o = new T1(Integer.parseInt(request.getParameter("f1")), request.getParameter("f2"), request.getParameter("f3"));
        // save object
        DB.saveOne(o);
        // redirect to GET
        response.sendRedirect("startpage");
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

web.xml (configuration of web application):

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <!-- define servlet -->
    <servlet>
        <servlet-name>TestController</servlet-name>
        <servlet-class>demo.TestController</servlet-class>
    </servlet>
    <!--  map servlet to /startpage -->
    <servlet-mapping>
        <servlet-name>TestController</servlet-name>
        <url-pattern>/startpage</url-pattern>
    </servlet-mapping>
    <!-- make /startpage default -->
    <welcome-file-list>  
        <welcome-file>startpage</welcome-file>  
    </welcome-file-list>  
</web-app>

Packaging of war file:

     0 Fri Dec 02 10:11:34 EST 2022 META-INF/
   109 Fri Dec 02 10:11:32 EST 2022 META-INF/MANIFEST.MF
     0 Fri Dec 02 10:11:34 EST 2022 WEB-INF/
   772 Wed Nov 23 20:16:04 EST 2022 WEB-INF/web.xml
     0 Fri Dec 02 10:11:34 EST 2022 WEB-INF/classes/
     0 Fri Dec 02 10:11:32 EST 2022 WEB-INF/classes/demo/
   762 Fri Dec 02 10:11:32 EST 2022 WEB-INF/classes/demo/DB.class
   746 Fri Dec 02 10:11:32 EST 2022 WEB-INF/classes/demo/T1.class
  1540 Fri Dec 02 10:11:32 EST 2022 WEB-INF/classes/demo/TestController.class
   831 Fri Nov 25 19:42:02 EST 2022 test.jsp

The result looks like:

JSP + servlet screen

Struts 1.x

Struts 1.x was the first widely used MVC web application framework and is helped promote MVC for web applications. So even though it is obsolete today, then it is still relevant for historic reasons.

Name and creator Apache Struts 1.x / open source
History Version 1.0 in 2000
In 2005 it was decided to base Struts 2.x on WebWork instead of Struts 1.x
Programming language Java
View technology JSP
Deployment Servlet container
Other technical characteristics
Status Obsolete since Struts 2.x was released in 2007

In Struts 1.x a Struts controller servlet forward to a specific action class that forward to a JSP view.

JSP is covered in detail in Java EE tricks.

The JSP view can be done multiple ways. I will show:

"old style"
Struts tags in the JSP (Struts is older than JSTL!) and a specific class for form fields
"new style"
JSTL tags in the JSP and use of the Map like DynaForm for form fields

Actions does not forward directly to views but to a logical name that get mapped to a view in Struts config.

Struts tags + form class

test.jsp (view):

<%@ taglib uri="struts-html" prefix="html" %>
<%@ taglib uri="struts-logic" prefix="logic" %>
<%@ taglib uri="struts-bean" prefix="bean" %>
<html>
<head>
<title>Struts 1</title>
</head>
<body>
<h1>Struts 1</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<logic:iterate id="o" name="data">
<tr>
<td><bean:write name="o" property="f1"/></td>
<td><bean:write name="o" property="f2"/></td>
<td><bean:write name="o" property="f3"/></td>
</tr>
</logic:iterate>
</table>
<h2>Add data:</h2>
<html:form action="/add">
F1: <html:text property="f1"/>
<br/>
F2: <html:text property="f2"/>
<br/>
F3: <html:select property="f3">
<html:optionsCollection name="opt"/>
</html:select>
<br/>
<html:submit>Add</html:submit>
</html:form>
</body>
</html>

ListAction.java (action for list):

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

// list data action
public class ListAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        // get data and store in request
        request.setAttribute("data", DB.getAll());
        // get options and store in request
        request.setAttribute("opt", Util.getOptions());
        // forward to configured list.ok action
        return mapping.findForward("list.ok");
    }
}

AddAction.java (action for add):

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

// add data action
public class AddAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        // get form data
        AddForm addfrm = (AddForm)form;
        int f1 = Integer.parseInt(addfrm.getF1());
        String f2 = addfrm.getF2();
        String f3 = addfrm.getF3();
        // store in database
        DB.saveOne(new T1(f1, f2, f3));
        // get data and store in request
        request.setAttribute("data", DB.getAll());
        // get options and store in request
        request.setAttribute("opt", Util.getOptions());
        // forward to configured add.ok action
        return mapping.findForward("add.ok");
    }
}

AddForm.java (form data for add):

package demo;

import org.apache.struts.action.ActionForm;

// form submit data
public class AddForm extends ActionForm {
    private String f1;
    private String f2;
    private String f3;
    public AddForm() {
        this("", "", "");
    }
    public AddForm(String f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public String getF1() {
        return f1;
    }
    public void setF1(String f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

Util.java (option list):

package demo;

import java.util.ArrayList;
import java.util.List;

public class Util {
    public static class Option {
        private String label;
        private String value;
        public Option() {
            this("", "");
        }
        public Option(String label, String value) {
            this.label = label;
            this.value = value;
        }
        public String getLabel() {
            return label;
        }
        public void setLabel(String label) {
            this.label = label;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }
    // option list
    public static List<Option> getOptions() {
        List<Option> res = new ArrayList<Option>();
        res.add(new Option("", ""));
        res.add(new Option(T1.VER, T1.VER));
        res.add(new Option(T1.NOTVER, T1.NOTVER));
        return res;
    }
}

web.xml (configuration of web application):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <!-- define Struts servlet -->
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <load-on-startup>5</load-on-startup>
    </servlet>
    <!-- map *.do to Struts servlet -->
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <!-- make /list.do default -->
    <welcome-file-list>  
        <welcome-file>list.do</welcome-file>  
    </welcome-file-list>  
    <!-- Strust taglibs -->
    <taglib>
        <taglib-uri>struts-html</taglib-uri>
        <taglib-location>/WEB-INF/lib/struts-html.tld</taglib-location>
    </taglib>
    <taglib>
        <taglib-uri>struts-bean</taglib-uri>
        <taglib-location>/WEB-INF/lib/struts-bean.tld</taglib-location>
    </taglib>
    <taglib>
        <taglib-uri>struts-logic</taglib-uri>
        <taglib-location>/WEB-INF/lib/struts-logic.tld</taglib-location>
    </taglib>
 </web-app>

struts-config.xml (configuration of Struts):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd" >
<struts-config>
    <form-beans>
        <form-bean name="addForm" type="demo.AddForm"/>
    </form-beans>
    <action-mappings>
        <action path="/list" type="demo.ListAction">
            <forward name="list.ok" path="/test.jsp"/>
        </action>
        <action path="/add" type="demo.AddAction" name="addForm" input="/test.jsp">
            <forward name="add.ok" path="/test.jsp"/>
        </action>
    </action-mappings>
    <message-resources parameter="messages.appResources" null="true"/>
</struts-config>

Packaging of war file:

     0 Fri Dec 02 10:38:34 EST 2022 META-INF/
   109 Fri Dec 02 10:38:32 EST 2022 META-INF/MANIFEST.MF
     0 Fri Dec 02 10:38:34 EST 2022 WEB-INF/
  1370 Wed Nov 23 20:25:00 EST 2022 WEB-INF/web.xml
     0 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/
     0 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/demo/
  1169 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/demo/AddAction.class
   648 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/demo/AddForm.class
   762 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/demo/DB.class
   861 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/demo/ListAction.class
   746 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/demo/T1.class
   598 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/demo/Util$Option.class
   588 Fri Dec 02 10:38:34 EST 2022 WEB-INF/classes/demo/Util.class
   812 Wed Nov 23 20:26:28 EST 2022 test.jsp
     0 Fri Dec 02 10:38:34 EST 2022 WEB-INF/lib/
  8868 Sun Jun 29 16:50:08 EDT 2003 WEB-INF/lib/struts-bean.tld
 66192 Sun Jun 29 16:50:14 EDT 2003 WEB-INF/lib/struts-html.tld
 14511 Sun Jun 29 16:50:06 EDT 2003 WEB-INF/lib/struts-logic.tld
498051 Sun Jun 29 16:50:00 EDT 2003 WEB-INF/lib/struts.jar
   721 Wed Nov 23 20:25:16 EST 2022 WEB-INF/struts-config.xml

The result looks like:

Struts 1.x screen

JSTL tags + DynaForm

test.jsp (view):

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Struts 1 with JSTL and DynaForm</title>
</head>
<body>
<f:view>
<h1>Struts 1 with JSTL and DynaForm</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<c:forEach items="${data}" var="o">
<tr>
<td><c:out value="${o.f1}"/></td>
<td><c:out value="${o.f2}"/></td>
<td><c:out value="${o.f3}"/></td>
</tr>
</c:forEach>
</table>
<h2>Add data:</h2>
<form method="post" action="add.do">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
F3: <select name="f3">
<c:forEach items="${options}" var="opt">
<option><c:out value="${opt}"/></option>
</c:forEach>
</select>
<br>
<input type="submit" value="Add"/>
<form>
</body>
</html>

ListAction.java (action for list):

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

// list data action
public class ListAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        // get data and store in request
        request.setAttribute("data", DB.getAll());
        // get options and store in request
        request.setAttribute("options", Util.getOptions());
        // forward to configured list.ok action
        return mapping.findForward("list.ok");
    }
}

AddAction.java (action for add):

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;

// add data action
public class AddAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws IOException, ServletException {
        // get form data
        DynaActionForm daf = (DynaActionForm)form;
        int f1 = Integer.parseInt((String)daf.get("f1"));
        String f2 = (String)daf.get("f2");
        String f3 = (String)daf.get("f3");
        // store in database
        DB.saveOne(new T1(f1, f2, f3));
        // get data and store in request
        request.setAttribute("data", DB.getAll());;
        // get options and store in request
        request.setAttribute("options", Util.getOptions());;
        // forward to configured add.ok action
        return mapping.findForward("add.ok");
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

Util.java:

package demo;

import java.util.ArrayList;
import java.util.List;

public class Util {
    // option list
    public static List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("");
        res.add(T1.VER);
        res.add(T1.NOTVER);
        return res;
    }
}

web.xml (configuration of web application):

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <!-- define Struts servlet -->
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <load-on-startup>5</load-on-startup>
    </servlet>
    <!-- map *.do to Struts servlet -->
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <!-- make /list.do default -->
    <welcome-file-list>  
        <welcome-file>list.do</welcome-file>  
    </welcome-file-list>  
 </web-app>

struts-config.xml (configuration of Struts):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd" >
<struts-config>
    <form-beans>
        <form-bean name="addForm" type="org.apache.struts.action.DynaActionForm">
            <form-property name="f1" type="java.lang.String"/>
            <form-property name="f2" type="java.lang.String"/>
            <form-property name="f3" type="java.lang.String"/>
        </form-bean>
    </form-beans>
    <action-mappings>
        <action path="/list" type="demo.ListAction">
            <forward name="list.ok" path="/test.jsp"/>
        </action>
        <action path="/add" type="demo.AddAction" name="addForm" input="/test.jsp">
            <forward name="add.ok" path="/test.jsp"/>
        </action>
    </action-mappings>
    <message-resources parameter="messages.appResources" null="true"/>
</struts-config>

Packaging of war file:

     0 Fri Dec 02 10:49:16 EST 2022 META-INF/
   109 Fri Dec 02 10:49:14 EST 2022 META-INF/MANIFEST.MF
     0 Fri Dec 02 10:49:16 EST 2022 WEB-INF/
   974 Wed Nov 23 21:19:44 EST 2022 WEB-INF/web.xml
     0 Fri Dec 02 10:49:16 EST 2022 WEB-INF/classes/
     0 Fri Dec 02 10:49:16 EST 2022 WEB-INF/classes/demo/
  1241 Fri Dec 02 10:49:16 EST 2022 WEB-INF/classes/demo/AddAction.class
   762 Fri Dec 02 10:49:16 EST 2022 WEB-INF/classes/demo/DB.class
   865 Fri Dec 02 10:49:16 EST 2022 WEB-INF/classes/demo/ListAction.class
   746 Fri Dec 02 10:49:16 EST 2022 WEB-INF/classes/demo/T1.class
   423 Fri Dec 02 10:49:16 EST 2022 WEB-INF/classes/demo/Util.class
   878 Wed Nov 23 21:20:12 EST 2022 test.jsp
     0 Fri Dec 02 10:49:16 EST 2022 WEB-INF/lib/
231320 Wed Nov 23 21:04:00 EST 2022 WEB-INF/lib/commons-beanutils-1.8.0.jar
 91699 Wed Nov 23 21:04:00 EST 2022 WEB-INF/lib/commons-chain-1.2.jar
329441 Wed Nov 23 21:04:00 EST 2022 WEB-INF/lib/struts-core-1.3.10.jar
   957 Wed Nov 23 20:44:02 EST 2022 WEB-INF/struts-config.xml

The result looks like:

Struts with JSTL tags screen

Struts 2.x:

Struts 2.x is based on WebWork not on Struts 1.x and even though it was a clear improvement then it never became as popular as Struts 1.x and the users moved to Spring MVC and JSF.

Name and creator Apache Struts 2.x / open source
History WebWork version 1.1 in 2002
Struts version 2.0 in 2007
Programming language Java
View technology JSP
Deployment Servlet container
Other technical characteristics
Status Actively maintained, but not widely used

In Struts 2.x a Struts controller servlet forward to a specific action class that forward to a JSP view (just like in Struts 1.x).

JSP is covered in detail in Java EE tricks.

test.jsp (view):

<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Struts 2</title>
</head>
<body>
<h1>Struts 2</h1>
<h2>Show data (iterator):</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<s:iterator value="data">
<tr>
<td><s:property value="f1"/></td>
<td><s:property value="f2"/></td>
<td><s:property value="f3"/></td>
</tr>
</s:iterator>
</table>
<h2>Add data:</h2>
<s:form action="add">
<s:textfield name="f1" label="F1"/>
<s:textfield name="f2" label="F2"/>
<s:select name="f3" label="F3" list="options"/>
<s:submit value="Add"/>
</s:form>
</body>
</html>

ListAction.java (action for list):

package demo;

import java.util.ArrayList;
import java.util.List;

import com.opensymphony.xwork2.ActionSupport;

public class ListAction extends ActionSupport {
    // execute action
    public String execute() {
        // forward to configured list view
        return "list";
    }
    // get data
    public List<T1> getData() {
        return DB.getAll();
    }
    // option list
    public List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("");
        res.add(T1.VER);
        res.add(T1.NOTVER);
        return res;
    }
}

AddAction.java (action for add):

package demo;

import com.opensymphony.xwork2.ActionSupport;

public class AddAction extends ActionSupport {
    // form data
    private String f1;
    private String f2;
    private String f3;
    public String getF1() {
        return f1;
    }
    public void setF1(String f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
    // execute action
    public String execute() {
        // save
        DB.saveOne(new T1(Integer.parseInt(f1), f2, f3));
        // forward to configured list redirect
        return "list";
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

web.xml (configuration of web application):

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <!-- define Struts 2 filter -->
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <!-- map everything to Struts 2 filter -->
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

struts.xml (configuration of Struts):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
    <constant name="struts.devMode" value="true" />
    <package name="demo" extends="struts-default">
        <default-action-ref name="list"/>
        <action name="list" class="demo.ListAction" method="execute">
            <result name="list">/test.jsp</result>
        </action>
        <action name="add" class="demo.AddAction" method="execute">
            <result name="list" type="redirect"><param name="location">list.action</param></result>
        </action>
    </package>
</struts>

Packaging of war file:

     0 Tue Dec 06 12:39:42 EST 2022 META-INF/
   109 Tue Dec 06 12:39:40 EST 2022 META-INF/MANIFEST.MF
     0 Tue Dec 06 12:39:42 EST 2022 WEB-INF/
   652 Mon Dec 05 20:00:30 EST 2022 WEB-INF/web.xml
     0 Tue Dec 06 12:39:42 EST 2022 WEB-INF/classes/
     0 Tue Dec 06 12:39:42 EST 2022 WEB-INF/classes/demo/
   780 Tue Dec 06 12:39:42 EST 2022 WEB-INF/classes/demo/AddAction.class
   762 Tue Dec 06 12:39:42 EST 2022 WEB-INF/classes/demo/DB.class
   634 Tue Dec 06 12:39:42 EST 2022 WEB-INF/classes/demo/ListAction.class
   746 Tue Dec 06 12:39:42 EST 2022 WEB-INF/classes/demo/T1.class
   614 Mon Dec 05 20:32:04 EST 2022 test.jsp
     0 Tue Dec 06 12:39:42 EST 2022 WEB-INF/lib/
 72446 Mon Dec 05 15:19:48 EST 2022 WEB-INF/lib/commons-fileupload-1.4.jar
214788 Mon Dec 05 15:19:50 EST 2022 WEB-INF/lib/commons-io-2.6.jar
501879 Mon Dec 05 15:19:50 EST 2022 WEB-INF/lib/commons-lang3-3.8.1.jar
1524587 Mon Dec 05 15:19:56 EST 2022 WEB-INF/lib/freemarker-2.3.28.jar
750581 Mon Dec 05 15:19:56 EST 2022 WEB-INF/lib/javassist-3.20.0-GA.jar
276771 Mon Dec 05 15:19:56 EST 2022 WEB-INF/lib/log4j-api-2.12.1.jar
262229 Mon Dec 05 15:19:58 EST 2022 WEB-INF/lib/ognl-3.1.26.jar
1642030 Mon Dec 05 15:20:04 EST 2022 WEB-INF/lib/struts2-core-2.5.22.jar
   681 Mon Dec 05 20:27:14 EST 2022 WEB-INF/classes/struts.xml

The result looks like:

Struts 2 screen

Stripes:

Stripes is another framework intended to replace Struts 1.x. It was designed to be simple and easy to use.

Name and creator Stripes / open source
History Version 1.0 in 2005
Programming language Java
View technology JSP
Deployment Servlet container
Other technical characteristics
Status Development stopped in 2015 and it is de facto EOL

In Stripes a Stripes controller servlet forward to a specific action class that forward to a JSP view.

JSP is covered in detail in Java EE tricks.

The actions can be mapped via *.action URL's or @UrlBinding annotations in Java code.

The JSP view can either be almost pure JSTL or use convenient Stripes tags.

.action URL + JSTL tags:

list.jsp (view):

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="stripes"	uri="http://stripes.sourceforge.net/stripes.tld"%>
<html>
<head>
<title>Stripes</title>
</head>
<body>
<h1>Stripes</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<c:forEach items="${actionBean.data}" var="o">
<tr>
<td><c:out value="${o.f1}"/></td>
<td><c:out value="${o.f2}"/></td>
<td><c:out value="${o.f3}"/></td>
</tr>
</c:forEach>
</table>
<h2>Add data:</h2>
<form method="post" action='<stripes:url beanclass="demo.AddActionBean"/>'>
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
F3: <select name="f3">
<c:forEach items="${actionBean.options}" var="opt">
<option><c:out value="${opt}"/></option>
</c:forEach>
</select>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

ListActionBean.java (action for list):

package demo;

import java.util.ArrayList;
import java.util.List;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;

public class ListActionBean implements ActionBean {
    // boilerplate (best practice is to move to BaseActionBean class)
    private ActionBeanContext ctx;
    public void setContext(ActionBeanContext ctx){
        this.ctx = ctx;
    }
    public ActionBeanContext getContext(){
        return ctx;
    }
    // actual action
    public ForwardResolution list() {
        // forward to list.jsp
        return new ForwardResolution("/list.jsp");
    }
    // get data
    public List<T1> getData() {
        return DB.getAll();
    }
    // option list
    public List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("");
        res.add(T1.VER);
        res.add(T1.NOTVER);
        return res;
    }
}

AddActionBean.java (action for add):

package demo;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;

public class AddActionBean implements ActionBean {
    // boilerplate (best practice is to move to BaseActionBean class)
    private ActionBeanContext ctx;
    public void setContext(ActionBeanContext ctx){
        this.ctx = ctx;
    }
    public ActionBeanContext getContext(){
        return ctx;
    }
    // form data
    private String f1;
    private String f2;
    private String f3;
    public String getF1() {
        return f1;
    }
    public void setF1(String f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
    // actual action
    public RedirectResolution add() {
        // save
        DB.saveOne(new T1(Integer.parseInt(f1), f2, f3));
        // redirect to list action
        return new RedirectResolution(ListActionBean.class);
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

web.xml (configuration of web application):

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
	     xmlns="http://java.sun.com/xml/ns/j2ee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <!-- define Stripes filter and dispatcher servlet -->
	<filter>
		<filter-name>StripesFilter</filter-name>
		<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
        <init-param>
            <param-name>ActionResolver.Packages</param-name>
            <param-value>demo</param-value>
        </init-param>
	</filter>
	<servlet>
        <servlet-name>StripesDispatcher</servlet-name>
        <servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>	
    <!-- map stripes filter and servlet -->
    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <servlet-name>StripesDispatcher</servlet-name>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    <servlet-mapping>
        <servlet-name>StripesDispatcher</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>
    <!-- make /demo/List.action default -->
    <welcome-file-list>  
        <welcome-file>demo/List.action</welcome-file>  
    </welcome-file-list>  
</web-app>

Packaging of war file:

     0 Tue May 23 19:45:12 EDT 2023 META-INF/
   109 Tue May 23 19:45:10 EDT 2023 META-INF/MANIFEST.MF
     0 Tue May 23 19:45:12 EDT 2023 WEB-INF/
  1602 Tue May 23 13:22:58 EDT 2023 WEB-INF/web.xml
     0 Tue May 23 19:45:12 EDT 2023 WEB-INF/classes/
     0 Tue May 23 19:45:12 EDT 2023 WEB-INF/classes/demo/
  1251 Tue May 23 19:45:12 EDT 2023 WEB-INF/classes/demo/AddActionBean.class
   762 Tue May 23 19:45:12 EDT 2023 WEB-INF/classes/demo/DB.class
  1072 Tue May 23 19:45:12 EDT 2023 WEB-INF/classes/demo/ListActionBean.class
   746 Tue May 23 19:45:12 EDT 2023 WEB-INF/classes/demo/T1.class
   960 Tue May 23 14:12:52 EDT 2023 list.jsp
     0 Tue May 23 19:45:12 EDT 2023 WEB-INF/lib/
  4017 Tue Dec 06 12:59:00 EST 2022 WEB-INF/lib/StripesResources.properties
 62050 Tue Dec 06 12:59:02 EST 2022 WEB-INF/lib/commons-logging-1.1.3.jar
 56404 Tue Dec 06 12:59:02 EST 2022 WEB-INF/lib/cos-05Nov2002.jar
554674 Tue Dec 06 12:59:02 EST 2022 WEB-INF/lib/stripes-1.6.0.jar

The result looks like:

Stripes screen

UrlBinding + Stripes tags:

list.jsp (view):

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="stripes"	uri="http://stripes.sourceforge.net/stripes.tld"%>
<html>
<head>
<title>Stripes (stripes form)</title>
</head>
<body>
<h1>Stripes (stripes form)</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<c:forEach items="${actionBean.data}" var="o">
<tr>
<td><c:out value="${o.f1}"/></td>
<td><c:out value="${o.f2}"/></td>
<td><c:out value="${o.f3}"/></td>
</tr>
</c:forEach>
</table>
<h2>Add data:</h2>
<stripes:form beanclass="demo.AddActionBean">
F1: <stripes:text name="f1"/>
<br>
F2: <stripes:text name="f2"/>
<br>
F3: <stripes:select name="f3">
<stripes:options-collection collection="${actionBean.options}"/>
</stripes:select>
<br>
<stripes:submit name="add" value="Add"/>
</stripes:form>
</body>
</html>

ListActionBean.java (action for list):

package demo;

import java.util.ArrayList;
import java.util.List;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.UrlBinding;

@UrlBinding("/demo/list")
public class ListActionBean implements ActionBean {
    // boilerplate (best practice is to move to BaseActionBean class)
    private ActionBeanContext ctx;
    public void setContext(ActionBeanContext ctx){
        this.ctx = ctx;
    }
    public ActionBeanContext getContext(){
        return ctx;
    }
    // actual action
    public ForwardResolution list() {
        // forward to list.jsp
        return new ForwardResolution("/list.jsp");
    }
    // get data
    public List<T1> getData() {
        return DB.getAll();
    }
    // option list
    public List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("");
        res.add(T1.VER);
        res.add(T1.NOTVER);
        return res;
    }
}

AddActionBean.java (action for add):

package demo;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.UrlBinding;

@UrlBinding("/demo/add")
public class AddActionBean implements ActionBean {
    // boilerplate (best practice is to move to BaseActionBean class)
    private ActionBeanContext ctx;
    public void setContext(ActionBeanContext ctx){
        this.ctx = ctx;
    }
    public ActionBeanContext getContext(){
        return ctx;
    }
    // form data
    private String f1;
    private String f2;
    private String f3;
    public String getF1() {
        return f1;
    }
    public void setF1(String f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
    // actual action
    public RedirectResolution add() {
        // save
        DB.saveOne(new T1(Integer.parseInt(f1), f2, f3));
        // redirect to list action
        return new RedirectResolution(ListActionBean.class);
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

web.xml (configuration of web application):

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
	     xmlns="http://java.sun.com/xml/ns/j2ee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <!-- define Stripes filter and dispatcher servlet -->
	<filter>
		<filter-name>StripesFilter</filter-name>
		<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
        <init-param>
            <param-name>ActionResolver.Packages</param-name>
            <param-value>demo</param-value>
        </init-param>
	</filter>
	<servlet>
        <servlet-name>StripesDispatcher</servlet-name>
        <servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>	
    <!-- map stripes filter and servlet -->
    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <servlet-name>StripesDispatcher</servlet-name>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    <servlet-mapping>
        <servlet-name>StripesDispatcher</servlet-name>
        <url-pattern>/demo/*</url-pattern>
    </servlet-mapping>
    <!-- make demolist default -->
    <welcome-file-list>  
        <welcome-file>demo/list</welcome-file>  
    </welcome-file-list>  
</web-app>

Packaging of war file:

     0 Tue May 23 19:50:52 EDT 2023 META-INF/
   109 Tue May 23 19:50:50 EDT 2023 META-INF/MANIFEST.MF
     0 Tue May 23 19:50:52 EDT 2023 WEB-INF/
  1585 Tue May 23 14:27:44 EDT 2023 WEB-INF/web.xml
     0 Tue May 23 19:50:52 EDT 2023 WEB-INF/classes/
     0 Tue May 23 19:50:52 EDT 2023 WEB-INF/classes/demo/
  1362 Tue May 23 19:50:52 EDT 2023 WEB-INF/classes/demo/AddActionBean.class
   762 Tue May 23 19:50:52 EDT 2023 WEB-INF/classes/demo/DB.class
  1184 Tue May 23 19:50:52 EDT 2023 WEB-INF/classes/demo/ListActionBean.class
   746 Tue May 23 19:50:52 EDT 2023 WEB-INF/classes/demo/T1.class
     0 Tue May 23 14:42:28 EDT 2023 WEB-INF/classes/StripesResources.properties
   939 Tue May 23 19:50:44 EDT 2023 list.jsp
     0 Tue May 23 19:50:52 EDT 2023 WEB-INF/lib/
  4017 Tue Dec 06 12:59:00 EST 2022 WEB-INF/lib/StripesResources.properties
 62050 Tue Dec 06 12:59:02 EST 2022 WEB-INF/lib/commons-logging-1.1.3.jar
 56404 Tue Dec 06 12:59:02 EST 2022 WEB-INF/lib/cos-05Nov2002.jar
554674 Tue Dec 06 12:59:02 EST 2022 WEB-INF/lib/stripes-1.6.0.jar

The result looks like:

Stripes screen

Spring MVC

Struts 2.x never became as popular as Struts 1.x - instead Spring MVC became the most widely used MVC web application framework.

Struts 2.x was not fully compatible with Struts 1.x and everybody was already using Spring for DI, so it made sense.

Spring MVC also supports RESTful web services, so it can be used for both traditional web applications and for HTML5 web applications.

Name and creator Spring Framework MVC / open source
History Spring 1.0 was introduced in 2002
Spring Boot 1.0 was released in 2014
Programming language Java
View technology JSP
Thymeleaf
Deployment Servlet container
Spring Boot
Other technical characteristics
Status Actively maintained

The Spring front controller forward to the specific controller that forward to the view - JSP or Thymeleaf.

JSP view

with JSTL tags

test.jsp (view):

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Spring with JSP</title>
</head>
<body>
<h1>Spring with JSP</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<c:forEach items="${data}" var="o">
<tr>
<td><c:out value="${o.f1}"/></td>
<td><c:out value="${o.f2}"/></td>
<td><c:out value="${o.f3}"/></td>
</tr>
</c:forEach>
</table>
<h2>Add data:</h2>
<form method="post">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
F3: <select name="f3">
<c:forEach items="${options}" var="opt">
<option><c:out value="${opt}"/></option>
</c:forEach>
</select>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

TestController.java (controller):

package demo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class TestController {
    // options
    private List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("");
        res.add(T1.VER);
        res.add(T1.NOTVER);
        return res;
    }
    @RequestMapping(value="/", method=RequestMethod.GET)
    public String get(Model m) {
        // get all data and save in request for use by JSP
        m.addAttribute("data", DB.getAll());
        // get options and save in request for JSP
        m.addAttribute("options", getOptions());
        // forward to test.jsp
        return "test";
    }
    @RequestMapping(value="/", method=RequestMethod.POST)
    public String post(@RequestParam("f1") String f1, @RequestParam("f2") String f2, @RequestParam("f3") String f3, Model m) {
        // store in database
        DB.saveOne(new T1(Integer.parseInt(f1), f2, f3));
        // get all data and save in request for use by JSP
        m.addAttribute("data", DB.getAll());
        // get options and save in request for JSP
        m.addAttribute("options", getOptions());
        // forward to test.jsp
        return "test";
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

web.xml (configure web application):

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <!-- define servlet -->
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--  map servlet to everything -->
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

spring-servlet.xml (configure Spring MVC):

<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:context = "http://www.springframework.org/schema/context"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:component-scan base-package="demo" />
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

Note that name of Spring config files is Spring-<servlet name in web.xml>.xml.

Packaging of war file:

     0 Fri Dec 02 12:53:40 EST 2022 META-INF/
   109 Fri Dec 02 12:53:38 EST 2022 META-INF/MANIFEST.MF
     0 Fri Dec 02 12:53:40 EST 2022 WEB-INF/
   680 Thu Nov 24 19:13:44 EST 2022 WEB-INF/web.xml
     0 Fri Dec 02 12:53:40 EST 2022 WEB-INF/classes/
     0 Fri Dec 02 12:53:40 EST 2022 WEB-INF/classes/demo/
   762 Fri Dec 02 12:53:40 EST 2022 WEB-INF/classes/demo/DB.class
   746 Fri Dec 02 12:53:40 EST 2022 WEB-INF/classes/demo/T1.class
  1541 Fri Dec 02 12:53:40 EST 2022 WEB-INF/classes/demo/TestController.class
     0 Fri Dec 02 12:53:40 EST 2022 WEB-INF/lib/
368839 Thu Nov 15 15:09:06 EST 2018 WEB-INF/lib/spring-aop-5.1.2.RELEASE.jar
 47352 Thu Nov 15 15:09:14 EST 2018 WEB-INF/lib/spring-aspects-5.1.2.RELEASE.jar
671376 Thu Nov 15 15:09:16 EST 2018 WEB-INF/lib/spring-beans-5.1.2.RELEASE.jar
1099343 Thu Nov 15 15:09:30 EST 2018 WEB-INF/lib/spring-context-5.1.2.RELEASE.jar
1289379 Thu Nov 15 15:09:56 EST 2018 WEB-INF/lib/spring-core-5.1.2.RELEASE.jar
280041 Thu Nov 15 15:10:10 EST 2018 WEB-INF/lib/spring-expression-5.1.2.RELEASE.jar
1375184 Thu Nov 15 15:11:02 EST 2018 WEB-INF/lib/spring-web-5.1.2.RELEASE.jar
798872 Thu Nov 15 15:11:36 EST 2018 WEB-INF/lib/spring-webmvc-5.1.2.RELEASE.jar
     0 Fri Nov 25 19:42:20 EST 2022 WEB-INF/jsp/
   821 Fri Nov 25 19:42:20 EST 2022 WEB-INF/jsp/test.jsp
   740 Thu Nov 24 19:11:42 EST 2022 WEB-INF/spring-servlet.xml

The result looks like:

Spring MVC with JSP view screen

with Spring tags

test.jsp (view):

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
<head>
<title>Spring with JSP and Spring form</title>
</head>
<body>
<h1>Spring with JSP and Spring form</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<c:forEach items="${data}" var="o">
<tr>
<td><c:out value="${o.f1}"/></td>
<td><c:out value="${o.f2}"/></td>
<td><c:out value="${o.f3}"/></td>
</tr>
</c:forEach>
</table>
<h2>Add data:</h2>
<form:form method="post" modelAttribute="addForm">
F1: <form:input path="f1"/>
<br>
F2: <form:input path="f2"/>
<br>
F3: <form:select path="f3" items="${options}"/>
<br>
<input type="submit" value="Add"/>
</form:form>
</body>
</html>

TestController.java (controller):

package demo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class TestController {
    // options
    private List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("");
        res.add(T1.VER);
        res.add(T1.NOTVER);
        return res;
    }
    @RequestMapping(value="/", method=RequestMethod.GET)
    public String get(Model m) {
        // setup empty form data
        m.addAttribute("addForm", new AddForm());
        // get all data and save in request for use by JSP
        m.addAttribute("data", DB.getAll());
        // get options and save in request for JSP
        m.addAttribute("options", getOptions());
        // forward to test.jsp
        return "test";
    }
    @RequestMapping(value="/", method=RequestMethod.POST)
    public String post(@ModelAttribute("addForm") AddForm addfrm, Model m) {
        // get form data
        int f1 = Integer.parseInt(addfrm.getF1());
        String f2 = addfrm.getF2();
        String f3 = addfrm.getF3();
         // store in database
        DB.saveOne(new T1(f1, f2, f3));
        // get all data and save in request for use by JSP
        m.addAttribute("data", DB.getAll());
        // get options and save in request for JSP
        m.addAttribute("options", getOptions());
        // forward to test.jsp
        return "test";
    }
}

AddForm.java (form data to add):

package demo;

// form submit data
public class AddForm {
    private String f1;
    private String f2;
    private String f3;
    public AddForm() {
        this("", "", "");
    }
    public AddForm(String f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public String getF1() {
        return f1;
    }
    public void setF1(String f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

web.xml (configuration of web application):

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <!-- define servlet -->
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--  map servlet to everything -->
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

spring-servlet.xml (configuration of Spring MVC):

<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:context = "http://www.springframework.org/schema/context"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:component-scan base-package="demo" />
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

Note that name of Spring config files is Spring-<servlet name in web.xml>.xml.

Packaging of war file:

     0 Fri Dec 02 13:12:12 EST 2022 META-INF/
   109 Fri Dec 02 13:12:10 EST 2022 META-INF/MANIFEST.MF
     0 Fri Dec 02 13:12:12 EST 2022 WEB-INF/
   680 Thu Nov 24 19:13:44 EST 2022 WEB-INF/web.xml
     0 Fri Dec 02 13:12:12 EST 2022 WEB-INF/classes/
     0 Fri Dec 02 13:12:12 EST 2022 WEB-INF/classes/demo/
   629 Fri Dec 02 13:12:12 EST 2022 WEB-INF/classes/demo/AddForm.class
   762 Fri Dec 02 13:12:12 EST 2022 WEB-INF/classes/demo/DB.class
   746 Fri Dec 02 13:12:12 EST 2022 WEB-INF/classes/demo/T1.class
  1612 Fri Dec 02 13:12:12 EST 2022 WEB-INF/classes/demo/TestController.class
     0 Fri Dec 02 13:12:12 EST 2022 WEB-INF/lib/
368839 Thu Nov 15 15:09:06 EST 2018 WEB-INF/lib/spring-aop-5.1.2.RELEASE.jar
 47352 Thu Nov 15 15:09:14 EST 2018 WEB-INF/lib/spring-aspects-5.1.2.RELEASE.jar
671376 Thu Nov 15 15:09:16 EST 2018 WEB-INF/lib/spring-beans-5.1.2.RELEASE.jar
1099343 Thu Nov 15 15:09:30 EST 2018 WEB-INF/lib/spring-context-5.1.2.RELEASE.jar
1289379 Thu Nov 15 15:09:56 EST 2018 WEB-INF/lib/spring-core-5.1.2.RELEASE.jar
280041 Thu Nov 15 15:10:10 EST 2018 WEB-INF/lib/spring-expression-5.1.2.RELEASE.jar
1375184 Thu Nov 15 15:11:02 EST 2018 WEB-INF/lib/spring-web-5.1.2.RELEASE.jar
798872 Thu Nov 15 15:11:36 EST 2018 WEB-INF/lib/spring-webmvc-5.1.2.RELEASE.jar
     0 Fri Nov 25 19:42:36 EST 2022 WEB-INF/jsp/
   867 Fri Nov 25 19:42:36 EST 2022 WEB-INF/jsp/test.jsp
   740 Thu Nov 24 19:11:42 EST 2022 WEB-INF/spring-servlet.xml

The result looks like:

Spring MVC JSP view Spring tags screen

Thymeleaf view

Thymeleaf is a new template engine (version 1.0 is from 2011) that can be used by Spring MVC instead of JSP.

Thymeleaf is valid XHTML just with some extra attributes.

In theory that makes it possible for HTML designers to work with the pages offline.

test.html (view):

<html xmlns:th="https://thymeleaf.org">
<head>
<title>Spring with Thymeleaf</title>
</head>
<body>
<h1>Spring with Thymeleaf</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<tr th:each="o: ${data}">
<td th:text="${o.f1}"/>
<td th:text="${o.f2}"/>
<td th:text="${o.f3}"/>
</tr>
</table>
<h2>Add data:</h2>
<form method="post" th:object="${addForm}">
F1: <input type="text" name="f1" th:field="*{f1}"/>
<br>
F2: <input type="text" name="f2" th:field="*{f2}"/>
<br>
F3: <select name="f3" th:field="*{f3}" items="${options}">
<option th:each="opt: ${options}" th:value="${opt}" th:text="${opt}"/>
</select>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

TestController.java (controller):

package demo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class TestController {
    // options
    private List<String> getOptions() {
        List<String> res = new ArrayList<String>();
        res.add("");
        res.add(T1.VER);
        res.add(T1.NOTVER);
        return res;
    }
    @RequestMapping(value="/", method=RequestMethod.GET)
    public String get(Model m) {
        // setup empty form data
        m.addAttribute("addForm", new AddForm());
        // get all data and save in request for use by JSP
        m.addAttribute("data", DB.getAll());
        // get options and save in request for JSP
        m.addAttribute("options", getOptions());
        // forward to test.jsp
        return "test";
    }
    @RequestMapping(value="/", method=RequestMethod.POST)
    public String post(@ModelAttribute("addForm") AddForm addfrm, Model m) {
        // get form data
        int f1 = Integer.parseInt(addfrm.getF1());
        String f2 = addfrm.getF2();
        String f3 = addfrm.getF3();
         // store in database
        DB.saveOne(new T1(f1, f2, f3));
        // get all data and save in request for use by JSP
        m.addAttribute("data", DB.getAll());
        // get options and save in request for JSP
        m.addAttribute("options", getOptions());
        // forward to test.jsp
        return "test";
    }
}

AddForm.java (form data for add):

package demo;

// form submit data
public class AddForm {
    private String f1;
    private String f2;
    private String f3;
    public AddForm() {
        this("", "", "");
    }
    public AddForm(String f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public String getF1() {
        return f1;
    }
    public void setF1(String f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

T1.java (data class):

package demo;

import java.io.Serializable;

// data class (row in database)
public class T1 implements Serializable {
    public static final String VER = "Verified";
    public static final String NOTVER = "Not verified";
    private int f1;
    private String f2;
    private String f3;
    public T1() {
        this(0, "", "");
    }
    public T1(int f1, String f2, String f3) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
    public String getF3() {
        return f3;
    }
    public void setF3(String f3) {
        this.f3 = f3;
    }
}

DB.java (simulated database):

package demo;

import java.util.ArrayList;
import java.util.List;

// simulated database
public class DB {
    private static List<T1> db;
    static {
        db = new ArrayList<T1>();
        db.add(new T1(1,"A", T1.VER));
        db.add(new T1(2,"BB", T1.VER));
        db.add(new T1(3,"CCC", T1.VER));
        db.add(new T1(4,"DDDD", T1.VER));
        db.add(new T1(5,"EEEEE", T1.NOTVER));
    }
    public static List<T1> getAll() {
        return db;
    }
    public static void saveOne(T1 o) {
        db.add(o);
    }
}

web.xml (configuration of web application):

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <!-- define servlet -->
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--  map servlet to everything -->
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

spring-servlet.xml (configuration of Spring MVC and Thymeleaf):

<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:context = "http://www.springframework.org/schema/context"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:component-scan base-package="demo" />
    <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
        <property name="prefix" value="/WEB-INF/html/" />
        <property name="suffix" value=".html" />
    </bean>
    <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />
    </bean>
    <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="templateEngine" ref="templateEngine" />
    </bean>
</beans>

with servlet container

Packaging of war file:

     0 Fri Dec 02 20:09:04 EST 2022 META-INF/
   109 Fri Dec 02 20:09:02 EST 2022 META-INF/MANIFEST.MF
     0 Fri Dec 02 20:09:04 EST 2022 WEB-INF/
   680 Thu Nov 24 19:13:44 EST 2022 WEB-INF/web.xml
     0 Fri Dec 02 20:09:04 EST 2022 WEB-INF/classes/
     0 Fri Dec 02 20:09:04 EST 2022 WEB-INF/classes/demo/
   629 Fri Dec 02 20:09:04 EST 2022 WEB-INF/classes/demo/AddForm.class
   762 Fri Dec 02 20:09:04 EST 2022 WEB-INF/classes/demo/DB.class
   746 Fri Dec 02 20:09:04 EST 2022 WEB-INF/classes/demo/T1.class
  1612 Fri Dec 02 20:09:04 EST 2022 WEB-INF/classes/demo/TestController.class
     0 Fri Dec 02 20:09:04 EST 2022 WEB-INF/lib/
246390 Fri Nov 25 16:23:26 EST 2022 WEB-INF/lib/attoparser-2.0.6.RELEASE.jar
 61409 Fri Nov 25 16:23:26 EST 2022 WEB-INF/lib/slf4j-api-2.0.3.jar
368839 Thu Nov 15 15:09:06 EST 2018 WEB-INF/lib/spring-aop-5.1.2.RELEASE.jar
 47352 Thu Nov 15 15:09:14 EST 2018 WEB-INF/lib/spring-aspects-5.1.2.RELEASE.jar
671376 Thu Nov 15 15:09:16 EST 2018 WEB-INF/lib/spring-beans-5.1.2.RELEASE.jar
1099343 Thu Nov 15 15:09:30 EST 2018 WEB-INF/lib/spring-context-5.1.2.RELEASE.jar
1289379 Thu Nov 15 15:09:56 EST 2018 WEB-INF/lib/spring-core-5.1.2.RELEASE.jar
280041 Thu Nov 15 15:10:10 EST 2018 WEB-INF/lib/spring-expression-5.1.2.RELEASE.jar
1375184 Thu Nov 15 15:11:02 EST 2018 WEB-INF/lib/spring-web-5.1.2.RELEASE.jar
798872 Thu Nov 15 15:11:36 EST 2018 WEB-INF/lib/spring-webmvc-5.1.2.RELEASE.jar
936897 Fri Nov 25 16:23:02 EST 2022 WEB-INF/lib/thymeleaf-3.1.0.RELEASE.jar
 47439 Fri Nov 25 16:23:04 EST 2022 WEB-INF/lib/thymeleaf-extras-springsecurity5-3.1.0.RELEASE.jar
186687 Fri Nov 25 16:23:02 EST 2022 WEB-INF/lib/thymeleaf-spring5-3.1.0.RELEASE.jar
173935 Fri Nov 25 16:23:26 EST 2022 WEB-INF/lib/unbescape-1.1.6.RELEASE.jar
     0 Fri Nov 25 19:56:36 EST 2022 WEB-INF/html/
   743 Fri Nov 25 19:56:36 EST 2022 WEB-INF/html/test.html
  1062 Fri Nov 25 19:16:50 EST 2022 WEB-INF/spring-servlet.xml

The result looks like:

Spring MVC Thymeleaf view screen

with Spring Boot

For Spring Boot deployment just use Maven for directory structure and build the jar file.

Maven generated jar file:

     0 Fri Dec 02 20:14:54 EST 2022 META-INF/
   528 Fri Dec 02 20:14:54 EST 2022 META-INF/MANIFEST.MF
     0 Fri Dec 02 20:14:54 EST 2022 org/
     0 Fri Dec 02 20:14:54 EST 2022 org/springframework/
     0 Fri Dec 02 20:14:54 EST 2022 org/springframework/boot/
     0 Fri Dec 02 20:14:54 EST 2022 org/springframework/boot/loader/
     0 Fri Dec 02 20:14:54 EST 2022 org/springframework/boot/loader/data/
  2688 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/data/RandomAccessDataFile$DataInputStream.class
     0 Fri Dec 02 20:14:54 EST 2022 org/springframework/boot/loader/jar/
  4976 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/AsciiBytes.class
  3263 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/data/RandomAccessDataFile$FileAccess.class
  1593 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/JarFileEntries$1.class
     0 Fri Dec 02 20:14:54 EST 2022 org/springframework/boot/loader/archive/
  3837 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/ExplodedArchive$FileEntryIterator.class
   273 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/ExplodedArchive$1.class
   282 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/data/RandomAccessDataFile$1.class
  2046 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/JarFileEntries$EntryIterator.class
  4015 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/data/RandomAccessDataFile.class
 14087 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/JarFileEntries.class
   485 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/data/RandomAccessData.class
   540 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/CentralDirectoryVisitor.class
   299 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/JarEntryFilter.class
  5243 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/ExplodedArchive.class
  1779 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/JarFileArchive$EntryIterator.class
  1081 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/JarFileArchive$JarFileEntry.class
  7336 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/JarFileArchive.class
  1953 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/PropertiesLauncher$PrefixMatchingArchiveFilter.class
  1484 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/PropertiesLauncher$ArchiveEntryFilter.class
   266 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/PropertiesLauncher$1.class
 19737 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/PropertiesLauncher.class
  4684 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/Launcher.class
  1502 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/MainMethodRunner.class
  3608 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/ExecutableArchiveLauncher.class
     0 Fri Dec 02 20:14:54 EST 2022 org/springframework/boot/loader/util/
  5203 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/util/SystemPropertyUtils.class
   616 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/Bytes.class
  1721 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/WarLauncher.class
  1585 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/JarLauncher.class
  1535 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
  5699 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/LaunchedURLClassLoader.class
   702 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/JarURLConnection$1.class
  5267 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/CentralDirectoryFileHeader.class
  4306 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/JarURLConnection$JarEntryName.class
  3116 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/CentralDirectoryEndRecord.class
  9854 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/JarURLConnection.class
  1813 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/ZipInflaterInputStream.class
  2062 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/JarFile$1.class
  4624 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/CentralDirectoryParser.class
 11949 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/jar/Handler.class
  1233 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/JarFile$2.class
  1374 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/JarFile$JarFileType.class
   302 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/Archive$Entry.class
 15076 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/JarFile.class
   945 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/Archive.class
  3619 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/JarEntry.class
   437 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/Archive$EntryFilter.class
   345 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/FileHeader.class
  1487 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/ExplodedArchive$FileEntryIterator$EntryComparator.class
  3650 Sat Jan 12 02:33:04 EST 2019 org/springframework/boot/loader/jar/StringSequence.class
  1102 Sat Jan 12 02:33:06 EST 2019 org/springframework/boot/loader/archive/ExplodedArchive$FileEntry.class
     0 Fri Dec 02 20:14:54 EST 2022 BOOT-INF/
     0 Fri Dec 02 20:14:54 EST 2022 BOOT-INF/classes/
     0 Fri Dec 02 20:14:50 EST 2022 BOOT-INF/classes/demo/
     0 Wed Nov 30 10:02:52 EST 2022 BOOT-INF/classes/templates/
     0 Fri Dec 02 20:14:54 EST 2022 META-INF/maven/
     0 Fri Dec 02 20:14:54 EST 2022 META-INF/maven/org.springframework.boot/
     0 Fri Dec 02 20:14:54 EST 2022 META-INF/maven/org.springframework.boot/web-thymeleaf/
  1009 Fri Dec 02 20:14:50 EST 2022 BOOT-INF/classes/demo/DB.class
   676 Fri Dec 02 20:14:50 EST 2022 BOOT-INF/classes/demo/Main.class
  1131 Fri Dec 02 20:14:50 EST 2022 BOOT-INF/classes/demo/AddForm.class
  1238 Fri Dec 02 20:14:50 EST 2022 BOOT-INF/classes/demo/T1.class
  2222 Fri Dec 02 20:14:50 EST 2022 BOOT-INF/classes/demo/TestController.class
   743 Wed Nov 30 10:02:52 EST 2022 BOOT-INF/classes/templates/test.html
  2676 Wed Nov 30 09:58:40 EST 2022 META-INF/maven/org.springframework.boot/web-thymeleaf/pom.xml
   105 Wed Nov 30 09:59:20 EST 2022 META-INF/maven/org.springframework.boot/web-thymeleaf/pom.properties
     0 Fri Dec 02 20:14:54 EST 2022 BOOT-INF/lib/
   405 Sat Jan 12 02:46:54 EST 2019 BOOT-INF/lib/spring-boot-starter-web-2.1.2.RELEASE.jar
   399 Sat Jan 12 02:46:36 EST 2019 BOOT-INF/lib/spring-boot-starter-2.1.2.RELEASE.jar
   407 Sat Jan 12 02:46:36 EST 2019 BOOT-INF/lib/spring-boot-starter-logging-2.1.2.RELEASE.jar
290339 Fri Mar 31 21:27:54 EDT 2017 BOOT-INF/lib/logback-classic-1.2.3.jar
471901 Fri Mar 31 21:27:16 EDT 2017 BOOT-INF/lib/logback-core-1.2.3.jar
 17524 Sun Jul 22 20:47:34 EDT 2018 BOOT-INF/lib/log4j-to-slf4j-2.11.1.jar
264060 Sun Jul 22 20:44:36 EDT 2018 BOOT-INF/lib/log4j-api-2.11.1.jar
  4596 Thu Mar 16 17:37:48 EDT 2017 BOOT-INF/lib/jul-to-slf4j-1.7.25.jar
 26586 Wed Feb 21 15:54:16 EST 2018 BOOT-INF/lib/javax.annotation-api-1.3.2.jar
301298 Mon Aug 27 16:23:36 EDT 2018 BOOT-INF/lib/snakeyaml-1.23.jar
   405 Sat Jan 12 02:46:54 EST 2019 BOOT-INF/lib/spring-boot-starter-json-2.1.2.RELEASE.jar
1347236 Sat Dec 15 21:59:06 EST 2018 BOOT-INF/lib/jackson-databind-2.9.8.jar
 66519 Sat Jul 29 20:53:26 EDT 2017 BOOT-INF/lib/jackson-annotations-2.9.0.jar
325619 Sat Dec 15 13:19:12 EST 2018 BOOT-INF/lib/jackson-core-2.9.8.jar
 33391 Sat Dec 15 23:06:14 EST 2018 BOOT-INF/lib/jackson-datatype-jdk8-2.9.8.jar
100674 Sat Dec 15 23:06:24 EST 2018 BOOT-INF/lib/jackson-datatype-jsr310-2.9.8.jar
  8642 Sat Dec 15 23:06:04 EST 2018 BOOT-INF/lib/jackson-module-parameter-names-2.9.8.jar
   406 Sat Jan 12 02:46:54 EST 2019 BOOT-INF/lib/spring-boot-starter-tomcat-2.1.2.RELEASE.jar
3252651 Thu Dec 06 21:15:22 EST 2018 BOOT-INF/lib/tomcat-embed-core-9.0.14.jar
249920 Thu Dec 06 21:15:24 EST 2018 BOOT-INF/lib/tomcat-embed-el-9.0.14.jar
263169 Thu Dec 06 21:15:24 EST 2018 BOOT-INF/lib/tomcat-embed-websocket-9.0.14.jar
1155701 Fri Jan 04 14:52:48 EST 2019 BOOT-INF/lib/hibernate-validator-6.0.14.Final.jar
 93107 Tue Dec 19 16:23:28 EST 2017 BOOT-INF/lib/validation-api-2.0.1.Final.jar
 66469 Wed Feb 14 13:23:28 EST 2018 BOOT-INF/lib/jboss-logging-3.3.2.Final.jar
 66540 Tue Mar 27 18:35:34 EDT 2018 BOOT-INF/lib/classmate-1.4.0.jar
1381418 Wed Jan 09 12:25:20 EST 2019 BOOT-INF/lib/spring-web-5.1.4.RELEASE.jar
672301 Wed Jan 09 12:24:30 EST 2019 BOOT-INF/lib/spring-beans-5.1.4.RELEASE.jar
800394 Wed Jan 09 12:26:00 EST 2019 BOOT-INF/lib/spring-webmvc-5.1.4.RELEASE.jar
368931 Wed Jan 09 12:24:44 EST 2019 BOOT-INF/lib/spring-aop-5.1.4.RELEASE.jar
1099300 Wed Jan 09 12:24:52 EST 2019 BOOT-INF/lib/spring-context-5.1.4.RELEASE.jar
280284 Wed Jan 09 12:24:46 EST 2019 BOOT-INF/lib/spring-expression-5.1.4.RELEASE.jar
   409 Sat Jan 12 02:47:12 EST 2019 BOOT-INF/lib/spring-boot-starter-thymeleaf-2.1.2.RELEASE.jar
177164 Sun Oct 28 22:43:38 EDT 2018 BOOT-INF/lib/thymeleaf-spring5-3.0.11.RELEASE.jar
869740 Sun Oct 28 22:35:36 EDT 2018 BOOT-INF/lib/thymeleaf-3.0.11.RELEASE.jar
244959 Fri Mar 30 09:34:24 EDT 2018 BOOT-INF/lib/attoparser-2.0.5.RELEASE.jar
173935 Fri Mar 30 17:04:26 EDT 2018 BOOT-INF/lib/unbescape-1.1.6.RELEASE.jar
 41203 Thu Mar 16 17:36:32 EDT 2017 BOOT-INF/lib/slf4j-api-1.7.25.jar
 39885 Tue Nov 27 21:19:34 EST 2018 BOOT-INF/lib/thymeleaf-extras-java8time-3.0.2.RELEASE.jar
1293220 Wed Jan 09 12:24:24 EST 2019 BOOT-INF/lib/spring-core-5.1.4.RELEASE.jar
23724 Wed Jan 09 12:24:14 EST 2019 BOOT-INF/lib/spring-jcl-5.1.4.RELEASE.jar
948314 Sat Jan 12 02:17:22 EST 2019 BOOT-INF/lib/spring-boot-2.1.2.RELEASE.jar
1256089 Sat Jan 12 02:26:00 EST 2019 BOOT-INF/lib/spring-boot-autoconfigure-2.1.2.RELEASE.jar
1142180 Sat Dec 29 21:04:18 EST 2018 BOOT-INF/lib/bootstrap-4.2.1.jar
164927 Fri Jun 10 10:33:04 EDT 2016 BOOT-INF/lib/jquery-3.0.0.jar
445320 Wed Aug 01 08:36:28 EDT 2018 BOOT-INF/lib/popper.js-1.14.3.jar

The result looks like:

Spring MVC Thymeleaf view screen

Grails:

Grails is a Groovy based RoR Style web application framework.

Name and creator Grails is an open source web application framework created by Graeme Rocher
History Version 0.1 in 2006
Version 1.0 in 2008
Programming language Groovy
View technology GSP (Groovy Server Pages)
Deployment Servlet container
Standalone
Other technical characteristics
Status Actively maintained with small but loyal community

Grails is a very traditional MVC framework with multiple controllers each with muiltiple actions and an action can either forward to a view or redirect to an URL.

Generation command:

grails create-app test
cd test
grails create-controller test

Run standalone command:

cd test
grails run-app --info

Create war command:

cd test
grails war

grails-app/controllers/test/TestController.groovy (controller):

package test

class TestController {
    def index() {
        [data: DB.getAll(), options: ["", T1.VER, T1.NOTVER]]
    }
    def add() {
        DB.saveOne(new T1(f1: Integer.parseInt(params.f1), f2: params.f2, f3: params.f3)) 
        redirect(action: "index")
    }
}
                                           

grails-app/domain/test/TestModel.groovy (model, simulated database):

package test

class T1 {
    static String VER = "Verified"
    static String NOTVER = "Not verified"
    int f1
    String f2
    String f3
}

class DB {
    static
    db = [ new T1(f1: 1, f2: "A", f3: T1.VER),
           new T1(f1: 2, f2: "BB", f3: T1.VER),
           new T1(f1: 3, f2: "CCC", f3: T1.VER),
           new T1(f1: 4, f2: "DDDD", f3: T1.VER),
           new T1(f1: 5, f2: "EEEEE", f3: T1.NOTVER)]
    static getAll() {
        return db
    }
    static saveOne(T1 o) {
        db.add(o)
    }
}

HTML form:

grails-app/views/test/index.gsp (view):

<html>
<head>
<title>Grails</title>
</head>
<body>
<h1>Grails</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<g:each in="${data}" var="o">
<tr>
<td>${o.f1}</td>
<td>${o.f2}</td>
<td>${o.f3}</td>
</tr>
</g:each>
</table>
<h2>Add data:</h2>
<form method="post" action="/test/add">
F1: <input type="text" name="f1">
<br>
F2: <input type="text" name="f2">
<br>
F3: <select name="f3">
<g:each in="${options}" var="opt">
<option>${opt}</option>
</g:each>
</select>
<br>
<input type="submit" value="Add"/>
</form>
</body>
</html>

The result looks like:

Grails with HTML form

GSP form:

grails-app/views/test/index.gsp (view):

<html>
<head>
<title>Grails - grails form</title>
</head>
<body>
<h1>Grails - grails form</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<g:each in="${data}" var="o">
<tr>
<td>${o.f1}</td>
<td>${o.f2}</td>
<td>${o.f3}</td>
</tr>
</g:each>
</table>
<h2>Add data:</h2>
<g:form url="[controller:'test',action:'add']">
F1: <g:textField name="f1"/>
<br>
F2: <g:textField name="f2"/>
<br>
F3: <g:select name="f3" from="${options}"/>
<br>
<g:actionSubmit value="Add"/>
</g:form>
</body>
</html>

The result looks like:

Grails with GSP form

ASP.NET MVC

ASP.NET MVC has replaced ASP.NET Web Forms as the .NET primary web application framework.

Name and creator ASP.NET MVC / Microsoft
History Version 1.0 in 2009
Version 3.0 in 2011
Version 5.0 in 2013
Programming language C#
VB.NET
View technology ASPX
Razor
Deployment IIS
Other technical characteristics
Status Obsolete with .NET 5.0 in 2020

ASP.NET MVC is very traditional MVC with a controller forwarding to a view - ASPX or Razor.

TestController.cs (controller):

using System;
using System.Web.Mvc;

namespace Demo.Controllers
{
    public class TestController : Controller
    {
        public ActionResult List()
        {
            return View(DB.GetAll());
        }
        public ActionResult Add(FormCollection frmcol)
        {
            string f1 = (string)frmcol["f1"];
            string f2 = (string)frmcol["f2"];
            string f3 = (string)frmcol["f3"];
            DB.SaveOne(new T1 { F1 = int.Parse(f1), F2 = f2, F3 = f3 });
            return RedirectToAction("List");
        }
    }
}

DB.cs (simulated database):

using System;
using System.Collections.Generic;

namespace Demo
{
    // data class (row in database)
    public class T1
    {
        public const String VER = "Verified";
        public const String NOTVER = "Not verified";
        public int F1 { get; set; }
        public string F2 { get; set; }
        public string F3 { get; set; }
    }
    // simulated database
    public class DB
    {
        private static IList<T1> db;
        static DB()
        {
            db  = new List<T1>();
            db.Add(new T1 { F1 = 1, F2 = "A", F3 = T1.VER });
            db.Add(new T1 { F1 = 2, F2 = "BB", F3 = T1.VER });
            db.Add(new T1 { F1 = 3, F2 = "CCC", F3 = T1.VER });
            db.Add(new T1 { F1 = 4, F2 = "DDDD", F3 = T1.VER });
            db.Add(new T1 { F1 = 5, F2 = "EEEEE", F3 = T1.NOTVER });
        }
        public static IList<T1> GetAll()
        {
            return db;
        }
        public static void SaveOne(T1 o)
        {
            db.Add(o);
        }
    }
}

Util.cs (option list):

using System;
using System.Web.Mvc;

namespace Demo
{
    public static class Util
    {
        public static SelectListItem[] GetOptions()
        {
            return new SelectListItem[] { new SelectListItem { Text = "", Value = "" },
                                          new SelectListItem { Text = T1.VER, Value  = T1.VER },
                                          new SelectListItem { Text = T1.NOTVER, Value = T1.NOTVER } };
        }
    }
}

Global.asax (configuration of web application - just referencing TestApplication class):

<%@ Application Inherits="Demo.TestApplication" %>

TestApplication.cs (real configuration of web application):

using System;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace Demo
{
    public class TestApplication : HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.Ignore("{resource}.axd/{*pathInfo}");
            routes.MapRoute(
                "Default",
                "{controller}/{action}/{id}",
                new {
                    controller = "Test",
                    action = "List",
                    id = UrlParameter.Optional
                });
        }
        protected void Application_Start()
        {
            ViewEngines.Engines.Add(new RazorViewEngine());
            RegisterRoutes(RouteTable.Routes);
        }
    }
}

It is really just setting up the route to the controller.

What to add to web.config somewhat depends on the software installed, but I had to add this fragment:

  <configSections>
        <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor">
            <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor" requirePermission="false"/>
            <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor" requirePermission="false"/>
        </sectionGroup>
    </configSections>
    <system.web>
        <compilation debug="true" targetFramework="4.5">
            <assemblies>
                <add assembly="System.Web.Mvc"/>
            </assemblies>
        </compilation>
        <pages>
            <namespaces>
                <add namespace="System.Web.Mvc"/>
                <add namespace="System.Web.Mvc.Html"/>
            </namespaces>
        </pages>
        <httpRuntime targetFramework="4.5" />
    </system.web>
    <system.web.webPages.razor>
        <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc"/>
        <pages pageBaseType="System.Web.Mvc.WebViewPage">
            <namespaces>
                <add namespace="System.Web.Mvc" />
                <add namespace="System.Web.Mvc.Html" />
                <add namespace="System.Web.Routing" />
            </namespaces>
        </pages>
    </system.web.webPages.razor>

The C# code either has to be placed in App_code dir or be compiled to a DLL and placed in bind ir.

The views has to exist as Views\<controller name>\<action name name><view type extenstion>.

ASPX view

List.aspx (view):

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="Demo" %>
<html>
<head>
<title>ASP.NET MVC with ASPX view</title>
</head>
<body>
<h1>ASP.NET MVC with ASPX view</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
<%
foreach(T1 o in (List<T1>)Model) {
%>
<tr>
<td><%=o.F1%></td>
<td><%=o.F2%></td>
<td><%=o.F3%></td>
</tr>
<%
}
%>
</table>
<h2>Add data:</h2>
<% using(Html.BeginForm("Add", "Test", FormMethod.Post)) { %>
F1: <%= Html.TextBox("f1") %>
<br/>
F2: <%= Html.TextBox("f2") %>
<br/>
F3: <%= Html.DropDownList("f3", Util.GetOptions()) %>
<br/>
<input type="submit" value="Add"/>
<% } %>
</body>
</html>

It is simple - <% %> around code blocks, <%= %> for values and some Html helper function. Very similar to ASP Classic!

The result looks like:

ASP.NET MVC ASPX view screen

Razor view

List.cshtml (view):

@using System.Collections.Generic
@using Demo
@model List<T1>
<html>
<head>
<title>ASP.NET MVC with Razor view</title>
</head>
<body>
<h1>ASP.NET MVC with Razor view</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
@foreach(T1 o in Model) {
<tr>
<td>@o.F1</td>
<td>@o.F2</td>
<td>@o.F3</td>
</tr>
}
</table>
<h2>Add data:</h2>
@using(Html.BeginForm("Add", "Test", FormMethod.Post)) {
<text>
F1: @Html.TextBox("f1")
<br/>
F2: @Html.TextBox("f2")
<br/>
F3: @Html.DropDownList("f3", Util.GetOptions())
<br/>
<input type="submit" value="Add"/>
</text>
}
</body>
</html>

It is very simple - HTML and C# are just mixed and C# is prefixed with @ to mark it as C#.

The text tag inside the form is required - otherwise the Fn: gets interpreted as C# resulting in a compilation error!

The result looks like:

ASP.NET MVC Razor view screen

ASP.NET Core MVC

ASP.NET Core MVC is the successor of ASP.NET MVC.

And it is 99% compatible with it.

Name and creator ASP.NET Core MVC / Microsoft
History Version 1.0 in 2016
Version 3.0 in 2019
Programming language C#
View technology Razor
Deployment ASP.NET Core Kestrel engine
Other technical characteristics
Status Actively maintained

List.cshtml (view):

@using System.Collections.Generic
@using Demo
@model List<T1>
<html>
<head>
<title>ASP.NET Core MVC</title>
</head>
<body>
<h1>ASP.NET Core MVC</h1>
<h2>Show data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
<th>F3</th>
</tr>
@foreach(T1 o in Model) {
<tr>
<td>@o.F1</td>
<td>@o.F2</td>
<td>@o.F3</td>
</tr>
}
</table>
<h2>Add data:</h2>
@using(Html.BeginForm("Add", "Test", FormMethod.Post)) {
<text>
F1: @Html.TextBox("f1")
<br/>
F2: @Html.TextBox("f2")
<br/>
F3: @Html.DropDownList("f3", Util.GetOptions())
<br/>
<input type="submit" value="Add"/>
</text>
}
</body>
</html>

It is very simple - HTML and C# are just mixed and C# is prefixed with @ to mark it as C#.

The text tag inside the form is required - otherwise the Fn: gets interpreted as C# resulting in a compilation error!

TestController.cs (controller):

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Demo.Controllers
{
    public class TestController : Controller
    {
        public ActionResult List()
        {
            return View(DB.GetAll());
        }
        public ActionResult Add(IFormCollection frmcol)
        {
            string f1 = (string)frmcol["f1"];
            string f2 = (string)frmcol["f2"];
            string f3 = (string)frmcol["f3"];
            DB.SaveOne(new T1 { F1 = int.Parse(f1), F2 = f2, F3 = f3 });
            return RedirectToAction("List");
        }
    }
}

The code is fundamentally the same as for ASP.NET MVC, but the namespaces are different and it is IFormCollection instead of FormCollection.

DB.cs (simulated database):

using System;
using System.Collections.Generic;

namespace Demo
{
    // data class (row in database)
    public class T1
    {
        public const String VER = "Verified";
        public const String NOTVER = "Not verified";
        public int F1 { get; set; }
        public string F2 { get; set; }
        public string F3 { get; set; }
    }
    // simulated database
    public class DB
    {
        private static IList<T1> db;
        static DB()
        {
            db  = new List<T1>();
            db.Add(new T1 { F1 = 1, F2 = "A", F3 = T1.VER });
            db.Add(new T1 { F1 = 2, F2 = "BB", F3 = T1.VER });
            db.Add(new T1 { F1 = 3, F2 = "CCC", F3 = T1.VER });
            db.Add(new T1 { F1 = 4, F2 = "DDDD", F3 = T1.VER });
            db.Add(new T1 { F1 = 5, F2 = "EEEEE", F3 = T1.NOTVER });
        }
        public static IList<T1> GetAll()
        {
            return db;
        }
        public static void SaveOne(T1 o)
        {
            db.Add(o);
        }
    }
}

Util.cs (option list):

using System;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace Demo
{
    public static class Util
    {
        public static SelectListItem[] GetOptions()
        {
            return new SelectListItem[] { new SelectListItem { Text = "", Value = "" },
                                          new SelectListItem { Text = T1.VER, Value  = T1.VER },
                                          new SelectListItem { Text = T1.NOTVER, Value = T1.NOTVER } };
        }
    }
}

Again same just different namespace compared to ASP.NET MVC.

Program.cs (main program):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Demo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(b =>
                {
                    b.UseStartup<Startup>();
                }).Build().Run();
        }
    }
    public class Startup
    {
        public IConfiguration Configuration { get; }
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(ep =>
            {
                ep.MapControllerRoute(name: "default",
                                      pattern: "{controller=Test}/{action=List}/{id?}");
            });
        }
    }
}

Basically just setup routing to the controller.

An ASP.NET Core MVC project can be created command line with:

dotnet new webapp

The result looks like:

ASP.NET Core MVC screen

Article history:

Version Date Description
1.0 December 3rd 2022 Initial version
1.1 December 6th 2022 Add Struts 2.x section
1.2 May 23rd 2023 Add Stripes section
1.3 January 15th 2024 Add Grails section

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj