In what cases is better to use DispatchActions than Action?
When you need many similar actions with similar form bean in the same struts module (e.g. CRUD actions to create read update delete the same object). With plain Action you'll need 4 Struts action files with imports, headers, method signatures:
// CreateAction.java
package com.example.package;
// imports and header
public class CreateAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// actual code
}
}
// ReadAction.java
package com.example.package;
// imports and header
public class ReadAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// actual code
}
}
// UpdateAction.java
package com.example.package;
// imports and header
public class UpdateAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// actual code
}
}
// DeleteAction.java
package com.example.package;
// imports and header
public class DeleteAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// actual code
}
}
Also you'll need 4 action mappings in struts-config.xml (of course if you are not using annotations). But in fact they will just invoke the next layer of code (manager/DAO/etc), which is independent from web (from request/response/mapping classes) and thus testable with unit tests allowing test-driven development and code reusability. All four classes will differ only in 1-2 lines of code. The rest is boilerplate, repeating again and again.
By adding additional parameters or reusing existing one in HTTP request (in other words: in JSP form tag) you can pack all 4 actions in one class, e.g. in EventDispatchAction:
// CRUDAction.java
package com.example.package;
// imports and header
public class CRUDAction extends EventDispatchAction {
public ActionForward create(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// actual code
}
public ActionForward read(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// actual code
}
public ActionForward update(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// actual code
}
public ActionForward delete(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// actual code
}
}
This requires much less boilerplate. In order to tell which action you want to perform, you can use submit buttons:
<html:submit property="update" value="Save" />
<html:submit property="delete" value="Delete" />
Related
I am writing unit tests for spring application that uses tiles, for one controller the forwardedUrl is different to view name, and for another controller they are the same but as far as I know the way everything is hooked up is the same.
Can anyone tell me why?
I have a controller method:
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView root(Locale locale, Model model) {
ModelAndView mv = new ModelAndView("base/index/view");
mv.addObject("display_title", "Home");
return mv;
}
And its unit test:
#Test
public void testApplicationRootUrl() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("base/index/view"))
.andExpect(forwardedUrl("/WEB-INF/views/base/index/view.jsp"));
}
The forwardedUrl is /WEB-INF/views/base/index/view.jsp so I would have expected the same pattern to apply to another controller.
Here I have another controller method (in a different controller):
#RequestMapping(value = "/products", method = RequestMethod.GET)
public ModelAndView getAllProducts(Locale locale, Model model) {
logger.info("Getting all products");
List<Product> allProducts = productService.getAllProducts();
ModelAndView mv = new ModelAndView("base/product_list/view");
mv.addObject("products", allProducts);
return mv;
}
And the unit test:
#Test
public void testGetAllProducts() throws Exception {
when(productService.getAllProducts()).thenReturn(getAllProducts());
mockMvc.perform(get("/products"))
.andExpect(status().isOk())
.andExpect(view().name("base/product_list/view"))
.andExpect(forwardedUrl("/WEB-INF/views/base/product_list/view.jsp"))
.andExpect(model().attributeExists("products"))
.andExpect(model().attribute("products", hasSize(1)))
.andExpect(model().attribute("products", hasItem(
allOf(
hasProperty("id", is(1)),
hasProperty("productName", is("Yellow")),
hasProperty("material", is("Wood"))
)
)));
verify(productService, times(1)).getAllProducts();
}
This test fails with the following assertion error, this is what I dont understand as tiles is used throughout the application so I would expect the forwardedUrl to remain consistent in terms of pattern:
java.lang.AssertionError: Forwarded URL expected:</WEB-INF/views/base/product_list/view.jsp> but was:<base/product_list/view>
If in the slim chance that someone ever wonders about this and wants to know the answer it is because of a difference in the way the mockMvc object is created for the tests.
For the navigation tests which do not have a mocked service I am using the WebApplicationContext:
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
However for the other tests which require a mocked service I am using Mockito and the standaloneSetup to build the mockMvc object:
#Mock
private ProductService productService;
#InjectMocks
private ProductController productController;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(productController).build();
}
It seems that they return different forwardedUrl's even though tiles is used throughout and there is no difference in the actual controllers, only in the tests.
I have ApiController with ExecuteAsync method overriden like this (I am using RavenDB and here I create its session):
public override async Task<HttpResponseMessage> ExecuteAsync(
HttpControllerContext controllerContext,
CancellationToken cancellationToken)
{
using (Session = Store.OpenAsyncSession())
{
var result = await base.ExecuteAsync(controllerContext, cancellationToken);
await Session.SaveChangesAsync();
return result;
}
}
I want to write automated Tests for it using xUnit or Microsoft Test Framework and at the moment I'm stuck with calling controllers action via ExecuteAsync.
Should I construct ControllerContext in any particular way to have that done or am I missing something?
Simply creating controller object and calling actions throws NullReferenceException for Session object, which is obviously not created as the ExecuteAsync has not been called.
Here's my cxf Rest WS :
#POST
#OPTIONS
#Path("/push")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
public Response push(#FormParam(value="agentId") String agentId);
I use it with a form :
<form id="form1" METHOD=POST ACTION="http://localhost:8080/uwv_interfacing-0.2.0-SNAPSHOT/api/rest/callHistory/push?AppKey=536f47d5-184f-3041-850c-bcad9f3afa49">
<input type="hidden" name="agentId" value="ofize">
<button type="submit" name="modifier" value="1">Submit</button>
</form>
I would like to get a HttpServletRequest instead of every fields, but when i replace "String agentId" with an HttpServletRequest it's empty, i have no fileds.
I've tried to replace the "#FormParam(value="agentId")" with "#Context" but i'm not sure about the good anotation to use.
Any idea?
You can inject MessageContext in your class like
import javax.ws.rs.core.Context;
import org.apache.cxf.jaxrs.ext.MessageContext;
...
#Context
private MessageContext messageContext;
...
// in your restful method, you could do something like
HttpServletRequest httpServletRequest = messageContext.getHttpServletRequest();
You can directly get the HttpServletRequest using #Context( qualified name: javax.ws.rs.core.Context) Hence your code shouls look like something as below.
#POST
#OPTIONS
#Path("/push")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
public Response push(#FormParam(value="agentId") String agentId, final #Context HttpServletRequest request);
I have a situation where I'm debating how to architect my controllers.
Consider the following controller:
public class FileSharingController : Controller
{
private readonly ICommandBus commandBus;
public FileSharingController(ICommandBus commandBus)
{
this.commandBus = commandBus;
}
[HttpPost]
public ActionResult PrepareMetadata(int blocksCount, string fileName, long fileSize)
{
...
}
[HttpPost]
public ActionResult ClearFileMetadata(string fileName){
...
}
[HttpPost] [ValidateInput(false)] //$.ajax({ data: html5FormDataFileChunk , processData: false ... })
public ActionResult UploadBlock(string fileName, int blockId){
var fileUploadCommand = (FileUploadCommand)ExtractFromSessionData(fileName);
var result = commandBus.Submit(fileUploadCommand);
...
}
public ActionResult CommitFileUploads(string[] filesToCommit){
var commitFileUploadCommand = (CommitFileUploadCommand)ExtractFromSessionData(fileName);
var result = commandBus.Submit(commitFileUploadCommand );
...
}
In this controller, I use the command pattern and pass a model to my commandBus which interfaces with my domain. The first three [HttpPost] methods on the controller are for handling jQuery ajax calls from a responsive file uploading UI.
Consider the situation where a user fills out a form (an interview) and uploads some files along with it. Although the user can upload the files before submitting the form, I don't want the uploaded files to be committed until AFTER they submit the form and it passes validation. That is why the last method on the controller is not an http endpoint. As such I have the following controller:
public class InterviewController : Controller
{
[HttpGet]
public ActionResult UserInterview()
{
InterviewViewModel viewModel = new InterviewViewModel ();
return PartialView(viewModel);
}
[HttpPost] [AllowAnonymous]
public ActionResult UserInterview(InterviewViewModel viewModel)
{
if(ModelState.IsValid)
{
var fileSharingController = new FileSharingController();
fileSharingController.CommitFileUploads(viewModel.Files);
}
return PartialView(viewModel);
}
}
The problem is I'm using IoC to inject a commandBus into the FileSharingController so I cannot just instantiate it with default constructor as I am doing.
My options to consider:
Create a custom controller factory to allow instantiating my controller anywhere in the code.
Turn my FileSharingController in a WebAPI controller and treat as a service
Which is the better design path for this situation? If the latter case, how can I keep the CommitFileUploads() method private? I don't want it to be exposed as an endpoint that can be triggered without first validating the rest of the form.
You can instantiate your controller like this:
ICommandBus commandBus = DependencyResolver.Current.GetService<ICommandBus>();
var fileShareController = new FileSharingController(commandBus);
Generic GetService() method is extension method, so make sure that you have "using System.Web.Mvc;" line in the cs file.
But then, it's better to have helper class that is responsible for keeping/storing already uploaded files, and call it from both controllers, instead instantiating controllers manually.
For example:
public class FileUploadManager
{
public FileUploadManager(ICommandBus commandBus, HttpSessionStateBase sessionState)
{
//....
}
}
and then you call it:
ICommandBus commandBus = DependencyResolver.Current.GetService<ICommandBus>();
var fileShareController = new FileUploadManager(commandBus, this.HttpContext.Session);
Or, if you don't want to use DependencyResolver, you pass ICommandBus to both controller's constructors, and use that reference to instantiate helper class.
simply just create the object of another conroller and use all its public methods.
I haven an entity with a collection, something like: Product.Parts - when I render a view for Product, I call .EditorFor(x => x.Parts), which names my fields like: Parts[0].Name.
how do I declare my controller method that receives the postback so it can recompose those field names into something useful?
I originally tried:
[HttpPost] public ActionResult Create(Product entity)
which seemed to give me an object of the right type but was all empty (properties were null). then I tried:
[HttpPost] public ActionResult Create(List<Product> entity)
but in this case entity itself is null. also, this didn't help:
[HttpPost] public ActionResult Create(IList entity)
I guess I can always do this:
[HttpPost] public ActionResult Create(FormCollection form)
{
var Name = form["Product[0].Name"];
}
but that's eecky! help?
your 1st attempt should have worked... maybe have a look at the posted values in firefox or chrome?