I'm implementing MVC using JSP and JDBC. I have imported a database class file to my JSP file and I would like to show the data of a DB table. I don't know how I should return the ResultSet from the Java class to the JSP page and embed it in HTML.
How can I achieve this?
In a well designed MVC approach, the JSP file should not contain any line of Java code and the servlet class should not contain any line of JDBC code.
Assuming that you want to show a list of products in a webshop, the following code needs to be created.
A Product class representing a real world entity of a product, it should be just a Javabean.
public class Product {
private Long id;
private String name;
private String description;
private BigDecimal price;
// Add/generate getters/setters/c'tors/equals/hashcode boilerplate.
}
A DAO class which does all the nasty JDBC work and returns a nice List<Product>.
public class ProductDAO {
private DataSource dataSource;
public ProductDAO(DataSource dataSource) {
this.dataSource = dataSource;
}
public List<Product> list() throws SQLException {
List<Product> products = new ArrayList<Product>();
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT id, name, description, price FROM product");
ResultSet resultSet = statement.executeQuery();
) {
while (resultSet.next()) {
Product product = new Product();
product.setId(resultSet.getLong("id"));
product.setName(resultSet.getString("name"));
product.setDescription(resultSet.getString("description"));
product.setPrice(resultSet.getBigDecimal("price"));
products.add(product);
}
}
return products;
}
}
A servlet class which obtains the list and puts it in the request scope.
#WebServlet("/products")
public class ProductsServlet extends HttpServlet {
#Resource(name="jdbc/YourDB") // For Tomcat, define as <Resource> in context.xml and declare as <resource-ref> in web.xml.
private DataSource dataSource;
private ProductDAO productDAO;
#Override
public void init() {
productDAO = new ProductDAO(dataSource);
}
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
List<Product> products = productDAO.list();
request.setAttribute("products", products); // Will be available as ${products} in JSP
request.getRequestDispatcher("/WEB-INF/products.jsp").forward(request, response);
} catch (SQLException e) {
throw new ServletException("Cannot obtain products from DB", e);
}
}
}
Finally a JSP file in /WEB-INF/products.jsp which uses JSTL <c:forEach> to iterate over List<Product> which is made available in EL by ${products}, and uses JSTL <c:out> to escape string properties in order to avoid XSS holes when it concerns user-controlled input.
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%# taglib uri="http://java.sun.com/jsp/jstl/format" prefix="fmt" %>
...
<table>
<c:forEach items="${products}" var="product">
<tr>
<td>${product.id}</td>
<td><c:out value="${product.name}" /></td>
<td><c:out value="${product.description}" /></td>
<td><fmt:formatNumber value="${product.price}" type="currency" currencyCode="USD" /></td>
</tr>
</c:forEach>
</table>
To get it to work, just call the servlet by its URL. Provided that the servlet is annotated #WebServlet("/products") or mapped in web.xml with <url-pattern>/products</url-pattern>, then you can call it by http://example.com/contextname/products
See also:
How to avoid Java code in JSP files?
doGet and doPost in Servlets
How should I connect to JDBC database / datasource in a servlet based application?
Design Patterns web based applications
RequestDispatcher.forward() vs HttpServletResponse.sendRedirect()
How to map a ResultSet with unknown amount of columns to a List and display it in a HTML table?
How do I pass current item to Java method by clicking a hyperlink or button in JSP page?
MVC, in a web application context, doesn't consist in using a class from a JSP. It consists in using the following model :
browser sends a request to a web server
the web server is configured so that the request is handled by a servlet or a filter (the controller : Java code, not JSP code)
The servlet/filter usually dispatches the request to a specific class (called an Action, the specific part of the controller), based on configuration/annotations
The action executes the business logic (i.e. fetch the data from the database in your example : the model)
The action forwards the request to a JSP. The role of the JSP is only to generate HTML code (i.e. display your data : the view)
Since the JSP usually uses JSP tags (the JSTL, for example) and the JSP expression language, and since JSP tags and the EL are designed to get information from JavaBeans, you'd better have your data available in the form of JavaBeans or collections of JavaBeans.
The role of the controller (the action class) is thus to fetch the data, to create JavaBean instances containing the data, in a suitable format for the JSP, to put them in request attributes, and then to dispatch to the JSP. The JSP will then iterate through the JavaBean instances and display what they contain.
You should not implement the MVC framework yourself. Use existing ones (Stripes, Struts, etc.)
I don't know how should I return the ResultSet from the class file to the JSP page
Well, you don't.
The point of MVC is to separate your model ( the M DB info in this case ) from your view ( V a jsp, in this case ) in such a way you can change the view without braking to application.
To do this you might use an intermediate object to represent your data ( usually called DTO - after Data Transfer Object -, don't know how they call it these days ), and other object to fetch it ( usually a DAO ).
So basically you have your JSP file, get the request parameters, and then invoke a method from the DAO. The dao, internally has the means to connect to the db and fetch the data and builds a collections of DTO's which are returned to the JSP for rendering.
Something like this extremely simplified ( and insecure ) code:
Employee.java
class Employee {
String name;
int emplid;
}
EmployeeDAO.java
class EmployeeDAO {
... method to connect
etc.
List<Employee> getAllNamed( String name ) {
String query = "SELECT name, emplid FROM employee where name like ?";
ResultSet rs = preparedStatement.executeQuery etc etc.
List<Employee> results = ....
while( rs.hasNext() ) {
results.add( new Employee( rs.getString("name"), rs.getInt("emplid")));
}
// close resources etc
return results;
}
}
employee.jsp
<%
request.setAttribute("employees", dao.getAllNamed( request.getParameter("name") );
%>
<table>
<c:forEach items="${employees}" var="employee">
<tr><td>${employee.emplid}</td><td>${employee.name}</td></tr>
</c:forEach>
</table>
I hope this give you a better idea.
I have a problem. I don't understand clearly the code. I have a similar problem with my code.
I have created database SQL and filled up. Then I want to implement a MainServlet (code below) that richieve data from database and in a different jsp page, I want to insert that data in section like h1, h2 ecc... I must use the ${} sintax but I don't know how do that.
Briefly, In jsp file (code below, I MUST USE ${} SINTAX) I want to "call" MainServlet and there I want to richieve data from database and view in jsp file.
I hope I have explained correctly, thank you very much!
MainServlet.java
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class MainServlet
*/
#WebServlet({ "/MainServlet" })
public class MainServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String PATH_JSP = "/WEB-INF/";
/**
* #see HttpServlet#HttpServlet()
*/
public MainServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* #see Servlet#init(ServletConfig)
*/
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
}
/**
* #see Servlet#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String doveAndare = request.getParameter("azione");
if(doveAndare==null)
doveAndare = "index";
try {
String driverString = "com.mysql.cj.jdbc.Driver";
Class.forName(driverString);
String connString = "jdbc:mysql://localhost:3306/ldd_jewels?user=root&password=";
Connection conn = DriverManager.getConnection(connString);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM JEWEL");
while (rs.next() == true) {
System.out.println(rs.getString("Category") + "\t" + rs.getString("Name"));
/* I try that but does not work
request.setAttribute("name", rs.getString("Name"));
javax.servlet.RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/widering_male.jsp");
dispatcher.forward(request, response); */
}
stmt.close();
conn.close();
} catch(Exception e) {
e.printStackTrace();
}
request.getRequestDispatcher(PATH_JSP+doveAndare+".jsp").forward(request, response);
}
/**
* #see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
doublerow.jsp
<section id="portfolio-details" class="portfolio-details">
<div class="container">
<div class="row gy-4">
<div class="col-lg-8">
<div class="portfolio-details-slider swiper">
<div class="swiper-wrapper align-items-center">
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_1.jpg" alt="" />
</div>
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_2.jpg" alt="" />
</div>
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_3.jpg" alt="" />
</div>
</div>
<div class="swiper-pagination"></div>
</div>
</div>
<div class="col-lg-4">
<div class="portfolio-info">
<h3>Product details</h3>
<ul>
<li><strong>Code</strong>: 1S3D5</li>
<li><strong>Category</strong>: Bracelets</li>
<li><strong>Name</strong>: Double Row Hinged Bangle</li>
<li><strong>Gender</strong>: Female</li>
<li><strong>Material</strong>: Yellow gold</li>
<li><strong>Size</strong>: 121mm</li>
<li><strong>Price</strong>: €5500</li>
</ul>
</div>
<div class="portfolio-description">
<h2>Description of product</h2>
<p>
The entwined ends of Tiffany Knot’s signature motif symbolize
the power of connections between people. Balancing strength
and elegance, each Tiffany Knot design is a complex feat of
craftsmanship. This bangle is crafted with yellow gold and
polished by hand for high shine. Wear on its own or partnered
with classic silhouettes for an unexpected pairing.
</p>
</div>
</div>
</div>
</div>
</section>
This is my database:
I want to insert each jewel in different pages (each jewel have a jsp file)
You can use the <c:forEach > tag
you can find a detailed example in the following link example use
I think it will be better for you to contain the data of the table into a collection such as list and return the list from the Java class and reuse this collection in the JSP.
Related
I am using ValidationMessage in a razor component to show validation message, like this:
<ValidationMessage For="#(() => ViewModel.CompanyNumber)" />
This generates this HTML code:
<div class="validation-message">The company number field is required.</div>
Is it possible to change the CSS-class? I want to use something else than validation-message. Adding class="myclass" is ignored by the controller. I've also tried with #attributes without success.
With .NET5 they added functionality to customize the validation classes on the actual input-fields (which issue 8695 was about) by way of setting a FieldCssClassProvider to the edit context. But there still seems to be no way of customizing the classes of the ValidationSummary or ValidationMessage components
Snipped directly from the .NET 5 docs
var editContext = new EditContext(model);
editContext.SetFieldCssClassProvider(new MyFieldClassProvider());
...
private class MyFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "good field" : "bad field";
}
}
Using this will yield the below html for an invalid input. At least with this we can style the actual input elements. Just not the messages...
<input class="bad field" aria-invalid="">
<div class="validation-message">Identifier too long (16 character limit).</div>
You can change the validation-message class inside the css file app.css inside the wwwroot. Or site.css in in earlier previews.
.validation-message {
color: red;
}
The class is set in ValidationMessage.cs
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
foreach (var message in CurrentEditContext.GetValidationMessages(_fieldIdentifier))
{
builder.OpenElement(0, "div");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", "validation-message");
builder.AddContent(3, message);
builder.CloseElement();
}
}
https://github.com/dotnet/aspnetcore/blob/master/src/Components/Web/src/Forms/ValidationMessage.cs
Why don't you just copy the code for ValidationMessage.cs and write in your own property? There is nothing special about this class except for capturing a Cascading Parameter. Just take this file and make your own with a slightly different name then add:
[Parameter] public string AdditionalClassNames {get;set;}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
foreach (var message in CurrentEditContext.GetValidationMessages(_fieldIdentifier))
{
builder.OpenElement(0, "div");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", string.IsNullOrEmpty(AdditionalClassNames) ? "validation-message" : $"validation-message {AdditionalClassNames}");
builder.AddContent(3, message);
builder.CloseElement();
}
}
https://github.com/dotnet/aspnetcore/blob/master/src/Components/Web/src/Forms/ValidationMessage.cs
EDIT
Even better, it's not sealed! Just use it as a base class for a new version and add what I mentioned above.
It is not possible in ASP.NET Core 3.1. Hopefully, it will be included in next major version, see this feature request:
https://github.com/dotnet/aspnetcore/issues/8695
I have a simple form like this which makes use of the #Html.EditorFor extension:
<form method="post">
#Html.EditorFor(x => x.SystemSettings.EmailFromAddress)
<submit-button title="Save"></submit-button>
</form>
I want to take advantage of .NET Core's tag helpers so that my form looks like this instead:
<form method="post">
<editor asp-for="SystemSettings.EmailFromAddress"/>
<submit-button title="Save"></submit-button>
</form>
I also eventually would like to have my own custom tag helpers so I can do something like this instead:
<text-box asp-for="SystemSettings.EmailFromAddress"></text-box>
I have a string template which gets rendered by the #Html.EditorFor extension:
#model string
<div class="form-group">
<label asp-for="#Model" class="m-b-none"></label>
<span asp-description-for="#Model" class="help-block m-b-none small m-t-none"></span>
<div class="input-group">
<input asp-for="#Model" class="form-control" />
<partial name="_ValidationIcon" />
</div>
<span asp-validation-for="#Model" class="validation-message"></span>
</div>
To do that, I saw someone implemented an EditorTagHelper, which looks like this:
[HtmlTargetElement("editor", TagStructure = TagStructure.WithoutEndTag,
Attributes = ForAttributeName)]
public class EditorTagHelper : TagHelper
{
private readonly IHtmlHelper _htmlHelper;
private const string ForAttributeName = "asp-for";
private const string TemplateAttributeName = "asp-template";
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
[HtmlAttributeName(TemplateAttributeName)]
public string Template { get; set; }
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; }
public EditorTagHelper(IHtmlHelper htmlHelper)
{
_htmlHelper = htmlHelper;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (output == null)
throw new ArgumentNullException(nameof(output));
if (!output.Attributes.ContainsName(nameof(Template)))
{
output.Attributes.Add(nameof(Template), Template);
}
output.SuppressOutput();
(_htmlHelper as IViewContextAware).Contextualize(ViewContext);
output.Content.SetHtmlContent(_htmlHelper.Editor(For.Name, Template));
await Task.CompletedTask;
}
}
When I use the EditorTagHelper though, it seems to be missing the unobtrusive Javascript validation attributes:
Using #Html.EditorFor, this gets rendered:
<input class="form-control valid" type="text" data-val="true" data-val-required="Email From Address cannot be empty" id="SystemSettings_EmailFromAddress" name="SystemSettings.EmailFromAddress" value="whatever#test.com" aria-required="true" aria-invalid="false" aria-describedby="SystemSettings_EmailFromAddress-error">
It's got the data-val attributes so client-side validation gets applied.
When I use the EditorTagHelper instead, this gets rendered:
<input class="form-control valid" type="text" id="SystemSettings_EmailFromAddress" name="SystemSettings.EmailFromAddress" value="whatever#test.com" aria-invalid="false">
The unobtrusive validation attributes are not being applied. I am using FluentValidation and I have specified an AbstractValidator like this:
public class SystemSettingsValidator : AbstractValidator<SystemSettings>
{
public SystemSettingsValidator()
{
RuleFor(x => x.EmailFromAddress).NotEmpty()
.WithMessage("Email From Address cannot be empty");
}
}
I found that if I removed the AbstractorValidator and simply added a [Required] attribute to my model property the validation then works properly. This suggests that there is something wrong with FluentValidation. Perhaps there is a configuration issue.
I am using Autofac dependency injection to scan my assemblies and register validator types:
builder.RegisterAssemblyTypes(Assembly.Load(assembly))
.Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
.AsImplementedInterfaces()
.PropertiesAutowired()
.InstancePerLifetimeScope();
This seems to work fine. In case it wasn't fine, I also tried registering the validators from the fluent validation options like this:
.AddFluentValidation(fv =>
{
fv.RegisterValidatorsFromAssemblies(new List<Assembly>
{Assembly.GetExecutingAssembly(), Assembly.Load(nameof(Entities))});
})
This also seemed to be fine.
One thing to note is that an earlier problem I had was that using Autofac assembly scanning was breaking the application when tag helpers were included. I added a filter to ensure that tag helpers are not included when registering these dependencies, e.g.
builder.RegisterAutowiredAssemblyInterfaces(Assembly.Load(Web))
.Where(x => !x.Name.EndsWith("TagHelper"));
I have uploaded a working sample of the code here: https://github.com/ciaran036/coresample2
Navigate to the Settings Page to see the field I am trying to validate.
This issue also appears to affect view components.
Thanks.
I believe the issue is in the tag helper, in that it uses IHtmlHelper.Editor rather than IHtmlHelper<TModel>.EditorFor to generate the HTML content. They are not quite the same.
As you point out FluentValidation injects the validation attributes as you'd expect for #Html.EditorFor(x => x.SystemSettings.EmailFromAddress). However for #Html.Editor("SystemSettings.EmailFromAddress"), which is what your custom tag helper is doing, FluentValidation doesn't inject the validation attributes. So that rules out the tag helper itself and moves the problem to the Editor invocation.
I also noticed that Editor doesn't resolve <label asp-for (or the other <span asp-description-for tag helper you're using) so that suggests it's not a FluentValidation specific issue.
I wasn't able to replicate your success with the Required attribute for the custom tag helper/Editor - the Required attribute only injected the validation attributes when using EditorFor.
The internals for Editor and EditorFor are similar but with a key difference, the way they resolve the ModelExplorer instance used to generate the HTML content differs and I suspect this is the problem. See below for these differences.
Things like PropertyName set to null and Metadata.Property not being set for Editor, but set to EmailFromAddress and SystemSettings.EmailFromAddress for EditorFor are standing out as potential causes for the behaviour we're seeing.
The painful part is the tag helper has a valid ModelExplorer instance via the For property. But there is no built in provision to provide it to the html helper.
As to the resolution, the obvious one seems to be to use EditorFor rather than Editor however it doesn't look easy. It'd likely involve reflection and building an expression.
Another option is, considering the tag helper resolves the ModelExplorer correctly, is to extend HtmlHelper and override the GenerateEditor method - what both Editor and EditorFor end up invoking - so you can pass in the ModelExplorer and work around the problem.
public class CustomHtmlHelper : HtmlHelper, IHtmlHelper
{
public CustomHtmlHelper(IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, IViewBufferScope bufferScope, HtmlEncoder htmlEncoder, UrlEncoder urlEncoder) : base(htmlGenerator, viewEngine, metadataProvider, bufferScope, htmlEncoder, urlEncoder) { }
public IHtmlContent CustomGenerateEditor(ModelExplorer modelExplorer, string htmlFieldName, string templateName, object additionalViewData)
{
return GenerateEditor(modelExplorer, htmlFieldName, templateName, additionalViewData);
}
protected override IHtmlContent GenerateEditor(ModelExplorer modelExplorer, string htmlFieldName, string templateName, object additionalViewData)
{
return base.GenerateEditor(modelExplorer, htmlFieldName, templateName, additionalViewData);
}
}
Update your tag helper to use it:
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (output == null)
throw new ArgumentNullException(nameof(output));
if (!output.Attributes.ContainsName(nameof(Template)))
{
output.Attributes.Add(nameof(Template), Template);
}
output.SuppressOutput();
(_htmlHelper as IViewContextAware).Contextualize(ViewContext);
var customHtmlHelper = _htmlHelper as CustomHtmlHelper;
var content = customHtmlHelper.CustomGenerateEditor(For.ModelExplorer, For.Metadata.DisplayName ?? For.Metadata.PropertyName, Template, null);
output.Content.SetHtmlContent(content);
await Task.CompletedTask;
}
Finally register the new helper, the earlier the better I'd say
services.AddScoped<IHtmlHelper, CustomHtmlHelper>();
Working solution
I have a very simple page that has 2 forms. When I submit one form it resets the other. There is some kind of hidden optimization is going on because when I refresh the page it presents the correct result.
Here is the page:
<div asp-validation-summary="All"></div>
<div class="col-md-3">
<form method="POST">
<fieldset>
<div>Host Name: <input asp-for="ClientConfig.HostName" /></div>
<div>Responses in HTML? <input type="checkbox" asp-for="ClientConfig.Html" /></div>
<input type="submit" asp-page-handler="ClientConfiguration" />
</fieldset>
</form>
<p>Base URL = #Model.ClientConfig.Summary</p>
</div>
<form method="POST">
<fieldset>
<div>Name: <input asp-for="Customer.Name" /></div>
<div>New? <input type="checkbox" asp-for="Customer.New" /></div>
<input type="submit" asp-page-handler="Customer" />
</fieldset>
</form>
<ul>
<li>Customer = #Model.Customer.Summary</li>
</ul>
Here is the model...
public class ClientConfig
{
public static ClientConfig Instance { get; set; } = new ClientConfig();
[Required, StringLength(100)] public string HostName { get; set; } = "LocalHost";
public bool Html { get; set; }
public string Summary => HostName + (Html ? " (Html)" : "");
}
public class Customer
{
public static Customer Instance { get; set; } = new Customer();
[Required, StringLength(100)] public string Name { get; set; } = "Default";
public bool New { get; set; }
public string Summary => Name + (New ? " (New)" : "");
}
public class IndexModel : PageModel
{
public IndexModel()
{
ClientConfig = ClientConfig.Instance;
Customer = Customer.Instance;
}
[BindProperty] public ClientConfig ClientConfig { get; set; }
[BindProperty] public Customer Customer { get; set; }
public async Task<IActionResult> OnPostCustomerAsync()
{
Customer.Instance = Customer;
return Page();
}
public async Task<IActionResult> OnPostClientConfigurationAsync()
{
ClientConfig.Instance = ClientConfig;
return Page();
}
}
So what is "return Page();" doing? According to the documentation it is simply rendering the current page. Not true. To verify this, simply refresh the page. It will be different, accurate with both forms filled in. Also if you replace "return Page()" with "return Redirect("/Index");" the result will also be accurate. So again, what is "return Page()" doing? There is some kind of undocumented optimization that resets all the forms except the one recently submitted.
You have multiple separate forms on your page with separate form values: In one form you are submitting the client configuration object, in the other you are submitting the customer object.
So when you are actually submitting a form, only that form's data is being submitted. For example, if you are submitting the customer form, the client configuration data is not being transferred in the POST request (and the other way around).
As such, when you render the page by returning Page(), only the data that is currently in the page model is being rendered. If you are submitting the customer form, then only the customer data is available (same for the client configuration form).
This happens simply because you only have partial data on a page where you would need more to fill in all forms. If you want to prevent that, you will have to combine the data into a single model and form.
Now, if you refresh the page in the browser, then your browser is typically smart enough not to clear form values immediately. If you do a hard refresh using Ctrl + F5, then the browser should also reset the values.
It's also possible that your browser is performing an auto-fill for the forms here. This will typically only apply for GET requests. So that could be the reason why you are getting this result when you return a Redirect() because that completes the form POST with a GET request.
When I submit one form it resets the other.
That's the expected behavior for the way you coded your page. When the form POSTs to the server, the server does three things:
creates a new IndexModel object using its constructor,
binds the object's properties to the POSTed form values, and
binds the object to its view.
In your code, step (1) resets properties to their default values. Step (2) overwrites those defaults with POSTed form values. Since you're submitting only one form, the other form's values retain their defaults. That's why submitting one resets the other.
So what is "return Page();" doing? According to the documentation it is simply rendering the current page. Not true. To verify this, simply refresh the page. It will be different, accurate with both forms filled in. Also if you replace "return Page()" with "return Redirect("/Index");" the result will also be accurate.
When you submit a form, return Page() renders the page in the context of a POST. On the other hand, when you refresh or redirect, the context is a GET. The difference you see happens because the context is different: the response to a POST is different from the response to a GET.
Right. After quite a long time of pondering this problem, I've finally figured it out. The problem: Razor pages moves in mysterious ways its wonders to perform.
My initial assumption was wrong. The page model constructor is not being bypassed. The page model is being properly constructed from static values. However after construction all bound objects on the page are reset. So this is not an "undocumented optimization"...it is an undocumented impairment.
The fix for this is to reset the page model from static values before returning Page().
public async Task<IActionResult> OnPostCustomerAsync()
{
Customer.Instance = Customer;
ClientConfig = ClientConfig.Instance;
return Page();
}
public async Task<IActionResult> OnPostClientConfigurationAsync()
{
ClientConfig.Instance = ClientConfig;
Customer = Customer.Instance;
return Page();
}
This is obviously a massive kluge, but no elegant solution exists. Anyone?
I am writing a android app including a webserver. Therefore I use the Embedded Jetty (8.0.1).
The next step I want to do is implementing a file upload.
The HTML looks like that and I think correct:
<form action=\"fileupload\" method=\"post\" enctype=\"multipart/form-data\">
<table>
<tbody>
<tr>
<td><input type=\"file\" name=\"userfile1\" /></td>
</tr>
<tr>
<td>
<input type=\"submit\" value=\"Submit\" /><input type=\"reset\" value=\"Reset\" />
</td>
</tr>
</tbody>
</table>
</form>
When I use this form, I can see in logcat that I received a file but I can't access this file in my Servlet.
I tried it with
File file1 = (File) request.getAttribute( "userfile1" );
and with the following function:
request.getParameter()
But everytime I receive a NULL Object. What I have to do?
As this is a multipart request, and you are uploading a "file" part, you need to grab the data using
request.getPart("userfile1");
For elements of your request that are not of type "file", for example input type="text", then you can access those properties through request.getParameter.
Just to note, Jetty 8.0.1 is quite old, the newest Jetty version (as of writing, 8.1.12) includes some important bugfixes to multipart handling.
If you upgrade your Jetty version, you will probably have to explicitly enable Multipart handling due to Jetty more strictly enforcing the Servlet 3.0 spec (https://bugs.eclipse.org/bugs/show_bug.cgi?id=395000), use the #MultipartConfig annotation if you are using servlets.
With handlers you have to manually add a Request.__MULTIPART_CONFIG_ELEMENT as an attribute to your request prior to calling getPart(s)
if (request.getContentType() != null && request.getContentType().startsWith("multipart/form-data")) {
baseRequest.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
}
This will allow you to parse the multipart requests, but the temp multipart files created will not be cleaned if you inject the config in this way. For servlets it is handled by a ServletRequestListener attached to the Request (see org.eclipse.jetty.server.Request#MultiPartCleanerListener).
So, what we do is have a HandlerWrapper early in our handler chain which adds the multipart config if needed and ensures the multipart files are cleaned after the request has finished. Example:
import java.io.IOException;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
/**
* Handler that adds the multipart config to the request that passes through if
* it is a multipart request.
*
* <p>
* Jetty will only clean up the temp files generated by
* {#link MultiPartInputStreamParser} in a servlet event callback when the
* request is about to die but won't invoke it for a non-servlet (webapp)
* handled request.
*
* <p>
* MultipartConfigInjectionHandler ensures that the parts are deleted after the
* {#link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
* method is called.
*
* <p>
* Ensure that no other handlers sit above this handler which may wish to do
* something with the multipart parts, as the saved parts will be deleted on the return
* from
* {#link #handle(String, Request, HttpServletRequest, HttpServletResponse)}.
*/
public class MultipartConfigInjectionHandler extends HandlerWrapper {
public static final String MULTIPART_FORMDATA_TYPE = "multipart/form-data";
private static final MultipartConfigElement MULTI_PART_CONFIG = new MultipartConfigElement(
System.getProperty("java.io.tmpdir"));
public static boolean isMultipartRequest(ServletRequest request) {
return request.getContentType() != null
&& request.getContentType().startsWith(MULTIPART_FORMDATA_TYPE);
}
/**
* If you want to have multipart support in your handler, call this method each time
* your doHandle method is called (prior to calling getParameter).
*
* Servlet 3.0 include support for Multipart data with its
* {#link HttpServletRequest#getPart(String)} & {#link HttpServletRequest#getParts()}
* methods, but the spec says that before you can use getPart, you must have specified a
* {#link MultipartConfigElement} for the Servlet.
*
* <p>
* This is normally done through the use of the MultipartConfig annotation of the
* servlet in question, however these annotations will not work when specified on
* Handlers.
*
* <p>
* The workaround for enabling Multipart support in handlers is to define the
* MultipartConfig attribute for the request which in turn will be read out in the
* getPart method.
*
* #see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=395000#c0">Jetty Bug
* tracker - Jetty annotation scanning problem (servlet workaround) </a>
* #see <a href="http://dev.eclipse.org/mhonarc/lists/jetty-users/msg03294.html">Jetty
* users mailing list post.</a>
*/
public static void enableMultipartSupport(HttpServletRequest request) {
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
}
#Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
boolean multipartRequest = HttpMethod.POST.is(request.getMethod())
&& isMultipartRequest(request);
if (multipartRequest) {
enableMultipartSupport(request);
}
try {
super.handle(target, baseRequest, request, response);
} finally {
if (multipartRequest) {
MultiPartInputStreamParser multipartInputStream = (MultiPartInputStreamParser) request
.getAttribute(Request.__MULTIPART_INPUT_STREAM);
if (multipartInputStream != null) {
try {
// a multipart request to a servlet will have the parts cleaned up correctly, but
// the repeated call to deleteParts() here will safely do nothing.
multipartInputStream.deleteParts();
} catch (MultiException e) {
// LOG.error("Error while deleting multipart request parts", e);
}
}
}
}
}
}
This can be used like:
MultipartConfigInjectionHandler multipartConfigInjectionHandler =
new MultipartConfigInjectionHandler();
HandlerCollection collection = new HandlerCollection();
collection.addHandler(new SomeHandler());
collection.addHandler(new SomeOtherHandler());
multipartConfigInjectionHandler.setHandler(collection);
server.setHandler(multipartConfigInjectionHandler);
The class MultiPartInputStreamParser is deprecated since jetty 9.4.11
it was replaced by MultiPartFormInputStream
the new code looks like this:
import java.io.IOException;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MultiPartFormInputStream;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
/**
* Handler that adds the multipart config to the request that passes through if
* it is a multipart request.
*
* <p>
* Jetty will only clean up the temp files generated by
* {#link MultiPartInputStreamParser} in a servlet event callback when the
* request is about to die but won't invoke it for a non-servlet (webapp)
* handled request.
*
* <p>
* MultipartConfigInjectionHandler ensures that the parts are deleted after the
* {#link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
* method is called.
*
* <p>
* Ensure that no other handlers sit above this handler which may wish to do
* something with the multipart parts, as the saved parts will be deleted on the return
* from
* {#link #handle(String, Request, HttpServletRequest, HttpServletResponse)}.
*/
public class MultipartConfigInjectionHandler extends HandlerWrapper {
public static final String MULTIPART_FORMDATA_TYPE = "multipart/form-data";
private static final MultipartConfigElement MULTI_PART_CONFIG = new MultipartConfigElement(
System.getProperty("java.io.tmpdir"));
public static boolean isMultipartRequest(ServletRequest request) {
return request.getContentType() != null
&& request.getContentType().startsWith(MULTIPART_FORMDATA_TYPE);
}
/**
* If you want to have multipart support in your handler, call this method each time
* your doHandle method is called (prior to calling getParameter).
*
* Servlet 3.0 include support for Multipart data with its
* {#link HttpServletRequest#getPart(String)} & {#link HttpServletRequest#getParts()}
* methods, but the spec says that before you can use getPart, you must have specified a
* {#link MultipartConfigElement} for the Servlet.
*
* <p>
* This is normally done through the use of the MultipartConfig annotation of the
* servlet in question, however these annotations will not work when specified on
* Handlers.
*
* <p>
* The workaround for enabling Multipart support in handlers is to define the
* MultipartConfig attribute for the request which in turn will be read out in the
* getPart method.
*
* #see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=395000#c0">Jetty Bug
* tracker - Jetty annotation scanning problem (servlet workaround) </a>
* #see <a href="http://dev.eclipse.org/mhonarc/lists/jetty-users/msg03294.html">Jetty
* users mailing list post.</a>
*/
public static void enableMultipartSupport(HttpServletRequest request) {
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
}
#Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
boolean multipartRequest = HttpMethod.POST.is(request.getMethod())
&& isMultipartRequest(request);
if (multipartRequest) {
enableMultipartSupport(request);
}
try {
super.handle(target, baseRequest, request, response);
} finally {
if (multipartRequest) {
String MULTIPART = "org.eclipse.jetty.servlet.MultiPartFile.multiPartInputStream";
MultiPartFormInputStream multipartInputStream = (MultiPartFormInputStream) request.getAttribute( MULTIPART );
if (multipartInputStream != null) {
multipartInputStream.deleteParts();
}
}
}
}
}
There is also an official example from the Jetty authors.
I have a MVC3 app that uses WCF services for data access. WCF services uses EF4.1 for data access.
I want minimum dependencies between MVC3 app and WCF services, so they don't share any libraries. The only dependency in MVC3 app is the service reference.
To validate entities on save, I defined the operation contracts on WCF services to generate FaultContract defined as below:
[OperationContract]
[FaultContract(typeof(EntityFault))]
void AddAddressEntity(Address entity);
EntityFault is defined as below:
[DataContract(IsReference=true)]
public class EntityFault
{
[DataMember]
public string ErrorMessage { get; set; }
[DataMember]
public virtual ICollection<ValidationErrorMessage> ValidationErrorMessages
{ get; set; }
}
and ValidationErrorMessage is a simple class with two properties, PropertyName and ValidationMessage
I trap DbEntityValidationException as below:
try
{ //....
db.SaveChanges();
}
catch (DbEntityValidationException ex)
{
EntityFault ef = EntityFaultHelper.CreateValidationFault(ex, entity);
throw new FaultException<EntityFault>(ef, ef.ErrorMessage);
}
In my MCV3 app I intercept the fault exception. But how can I display the error messages either in
#Html.ValidationMessageFor(<my specific field>)
or in
#Html.ValidationSummary(...)
section?
If the model fields were annotated, or if the client entity implements IValidatableObject, error msgs are displayed in specified areas.
One idea is to use ViewBag, and define display placeholders for error msgs received from WCF's FaultContract, and set the corresponding ViewPag dinamic properties for received error msgs.
But I'm wondering if there's a better approach.
Thanks
So far I found the following solution
I added for each field in the view a placeholder to display error message as below:
<div class="editor-label">
#Html.LabelFor(model => model.State)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.State)
#ViewBag.StateError
</div>
Then in the controller I have this code
using (var addressClient = new AddressServiceClient(_configName))
{
try
{
addressClient.AddAddressEntity(address);
return RedirectToAction("Index");
}
catch (FaultException<EntityFault> ex)
{
foreach (var err in ex.Detail.ValidationErrorMessages)
{
ViewData.Add(
string.Format("{0}Error", err.PropertyName),
err.ErrorMessage);
}
}
}
And it display the errors right next to fields.
But I'm still wondering if there's a way to use the placeholders
#Html.ValidationMessageFor(model => model.Address1)
or
#Html.ValidationSummary(true)
The reason is, I don't want to have to manually change all the Create / Edit views generated by MVC3 VS helper, I prefer to find a way to reuse those placeholders.
Edit
I found a better solution. It works directly with the view generated by MVC out of the box.
The key code is to set the error messages in ModelState as below
try
{
addressClient.AddAddressEntity(address);
return RedirectToAction("Index");
}
catch (FaultException<EntityFault> ex)
{
foreach (var err in ex.Detail.ValidationErrorMessages)
this.ModelState.AddModelError(err.PropertyName, err.ErrorMessage);
}
The point is in ModelState there is a key for each model field (property), with property name as key. So by adding model error for that property, the error message is displayed in the corresponding place for validation error