Struts2 more than one action in one class - struts

I'm using Struts2. I have two web forms that have the same code. I would like to eliminate one form. Here is the structure of my Struts project.
\Web Pages
form.jsp
\WEB-INF
\Content
error.jsp
form.jsp
success.jsp
\Source Packages
\action
MyAction.java
MyAction.java
package action;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.*;
public class MyAction extends ActionSupport {
#Action(value = "foo", results = {
#Result(name = "input", location = "form.jsp"),
#Result(name = "success", location = "success.jsp"),
#Result(name = "error", location = "error.jsp")
})
public String execute() throws Exception {
if (user.length() == 1) {
return "success";
} else {
return "error";
}
}
private String user = "";
public void validate() {
if (user.length() == 0) {
addFieldError("user", getText("user required"));
}
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
}
I tried to eliminate form.jsp under \Web Pages by adding a new action method to MyAction.java.
#Action(value="bar", results = {
#Result(name = "success", location = "form.jsp"),
})
public String another() {
return "success";
}
But I got the following error when I go to http : //localhost .../bar.action
HTTP Status 404 - No result defined for action action.MyAction and result input

Your MyAction has an implementation of validate(), which means it is validation aware.
What's happening is that you're calling another, but validate() is kicking in (as it's in the interceptor stack). Validation is failing, and therefore sending to INPUT result, which is not defined in another.
You should
Add #SkipValidation to the another method if you don't want validation there
Add the INPUT result to another() if you want a default input result
On a more general note, when you get that kind of error (No result defined for action X and result input) it usually means you're either having validation errors, parameter population errors (eg: an exception in preparable).

Related

Upgrade Solution to use FluentValidation Ver 10 Exception Issue

Please I need your help to solve FluentValidation issue. I have an old desktop application which I wrote a few years ago. I used FluentValidation Ver 4 and Now I'm trying to upgrade this application to use .Net framework 4.8 and FluentValidation Ver 10, but unfortunately, I couldn't continue because of an exception that I still cannot fix.
I have this customer class:
class Customer : MyClassBase
{
string _CustomerName = string.Empty;
public string CustomerName
{
get { return _CustomerName; }
set
{
if (_CustomerName == value)
return;
_CustomerName = value;
}
}
class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(obj => obj.CustomerName).NotEmpty().WithMessage("{PropertyName} is Empty");
}
}
protected override IValidator GetValidator()
{
return new CustomerValidator();
}
}
This is my base class:
class MyClassBase
{
public MyClassBase()
{
_Validator = GetValidator();
Validate();
}
protected IValidator _Validator = null;
protected IEnumerable<ValidationFailure> _ValidationErrors = null;
protected virtual IValidator GetValidator()
{
return null;
}
public IEnumerable<ValidationFailure> ValidationErrors
{
get { return _ValidationErrors; }
set { }
}
public void Validate()
{
if (_Validator != null)
{
var context = new ValidationContext<Object>(_Validator);
var results = _Validator.Validate(context); **// <======= Exception is here in this line**
_ValidationErrors = results.Errors;
}
}
public virtual bool IsValid
{
get
{
if (_ValidationErrors != null && _ValidationErrors.Count() > 0)
return false;
else
return true;
}
}
}
When I run the application test I get the below exception:
System.InvalidOperationException HResult=0x80131509 Message=Cannot
validate instances of type 'CustomerValidator'. This validator can
only validate instances of type 'Customer'. Source=FluentValidation
StackTrace: at
FluentValidation.ValidationContext1.GetFromNonGenericContext(IValidationContext context) in C:\Projects\FluentValidation\src\FluentValidation\IValidationContext.cs:line 211 at FluentValidation.AbstractValidator1.FluentValidation.IValidator.Validate(IValidationContext
context)
Please, what is the issue here and How can I fix it?
Thank you
Your overall implementation isn't what I'd consider normal usage however the problem is that you're asking FV to validate the validator instance, rather than the customer instance:
var context = new ValidationContext<Object>(_Validator);
var results = _Validator.Validate(context);
It should start working if you change it to:
var context = new ValidationContext<object>(this);
var results = _Validator.Validate(context);
You're stuck with using the object argument for the validation context unless you introduce a generic argument to the base class, or create it using reflection.

FakeItEasy ControllerTest HttpGet Calls

I want to start using FakeItEasy for testing queries.
The tests I want to write should check if entities are returned on HttpGet calls (get all and get by Id)
The Controller:
public class ToDoController : ControllerBase
{
private readonly IMediator _mediator;
public ToDoController(IMediator mediator) =>
_mediator = mediator;
[HttpGet]
[Produces("application/json")]
[ProducesResponseType(typeof(IEnumerable<ToDoItem>), (int)HttpStatusCode.OK)]
public async Task<ActionResult<IEnumerable<ToDoItem>>> Get()
{
var result = await _mediator.Send(new ToDoItemsQuery(new
AllToDoItems())).ConfigureAwait(false);
if (result != null && result.Any())
{
return result.ToList();
}
throw new InvalidOperationException("TODO: error handling");
}
[HttpGet]
[Route("{id}")]
[Produces("application/json")]
[ProducesResponseType(typeof(ToDoItem), (int)HttpStatusCode.OK)]
public async Task<ActionResult<ToDoItem>> GetById(int itemId)
{
var result = await _mediator
.Send(new ToDoItemsQuery(new ToDoItemById(itemId)))
.ConfigureAwait(false);
if (result != null && result.Any())
{
return result.FirstOrDefault();
}
throw new InvalidOperationException("TODO: error handling");
}
}
}
The TestClass:
public class ToDoItemControllerTests : ControllerTestBase
{
private IMediator _mediator;
private ToDoController _sut;
public ToDoItemControllerTests()
{
_mediator = A.Fake<IMediator>();
_sut = new ToDoController(_mediator);
}
[TestMethod]
public async Task GetAllItemsAsync_SuccessTest()
{
A.CallTo(() => _mediator.Send(A<AllToDoItems>._,
A<CancellationToken>._)).Returns(A.CollectionOfFake<ToDoItem>(10));
var result = await _sut.Get();
Assert.IsNotNull(result);
A.CallTo(() => _mediator).MustHaveHappened();
}
[TestMethod]
public async Task GetItemByIdAsync_SuccessTest()
{
// Arrange
int itemId = 2;
var commandResult =
new List<ToDoItem>
{
new ToDoItem
{
Id = itemId
};
}
A.CallTo(() => MediatR.Send(A<ToDoItemById>._, A<CancellationToken>._)).Returns(commandResult);
// Act
var result = await _sut.GetById(itemId);
// Assert
Assert.IsNotNull(result);
A.CallTo(() => MediatR.Send(A<ToDoItemById>._, A<CancellationToken>._)).MustHaveHappened();
}
}
So in the first test I set up A.CallTo the interface IMediatR to return 10 ToDoItems.
During debug I see the _sut.Get() enter the controller, entering the correct method/api call.
The _mediator.Send() in the controller returns a Fake IEnumerable (not the 10 items i set up in the first Call.To in the testmethod, but an enumeration that yields no results).
Because of the result.Any() being false, the controller throws an InvalidOperationException
And I cannot even Assert the result.IsNotNull()
The second test I want to Test If 1 item is returned upon calling the API.
I set up (a) an itemId of type int for parameter,
(b) A mocked(?) List with 1 Item from the setup with the itemId and
(c) a call to the mediatR should return the mocked Listfrom (b)
I make the call from the test, in debug I see the call await _mediator.Sent() returning
A Fake Ienumerable of ToDoItem, result is not null, but because result.Any() is false,
the item doesn't get returned, and I get another InvalidOperationException
I feel like I'm missing something in the setup of the tests..
A Fake database Interface?
I don't want to chance my controller and make the if less restrictive, just so my test would pass
EDIT:
Even if I change the if condition to removing the Any condition
and I see the test entering the controller, returning "First Or Default" of the result,
The test fails on A Call To Must Have Happened.
Expected to find it once or more but no calls were made to the fake object.
This I really don't get, i actually see him making the call?!
I've browsed GitHub to find examples but the closest I found was Entities with methods, with those methods being defined in an interface. This is not the case here
Seeing as the official documentation doesn't make me any wiser I turn to SO <3
Thanks in advance!
In the first test, you configure the call to Send with an argument of type AllToDoItems. But in the controller, you actually call Send with a TodoItemsQuery. So the call doesn't match, and the default (unconfigured) behavior, which is to return a fake IEnumerable, applies. You need to configure the call like this:
A.CallTo(() => _mediator.Send(A<TodoItemsQuery>._,
A<CancellationToken>._)).Returns(A.CollectionOfFake<ToDoItem>(10));
In the second test, the problem is the same, with ToDoItemById instead of AllToDoItems

How to get a custom ModelState error message in ASP.NET Core when a wrong enum value is passed in?

I'm passing a model to an API action with a property called eventType which is a nullable custom enum.
If I pass a random value for eventType, such as 'h', it fails to serialise which is correct.
However, the error I get from the ModelState is not something I would want a public caller to see. It includes the line number and position (see below).
I've tried a number of options including a custom data annotation with no success.
Does anyone know how I could define a nicer custom message?
"Error converting value \"h\" to type
'System.Nullable`1[Custom.EventTypes]'. Path 'eventType', line 1,
position 80."
Most times the first error is usually the most important error or rather one that describes the situation properly. You can use this way to manipulate to get the first error message from the first key or change it to whatever you want if you wish to get all the error messages.
public ActionResult GetMyMoney(MyModel myModel)
{
string nss = ModelState.First().Key;
ModelError[] ern = ModelState[nss].Errors.ToArray();
string ndd = ern.First().ErrorMessage;
}
public class CustomFilter: IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
// You can pass custom object to BadRequestObjectResult method
context.Result = new BadRequestObjectResult(customObject);
}
}
}
You can write a custom filter like above mentioned and pass a custom object with your message.
Ref: this
IF you just want the error messages you can simply create a custom class of response and then
var response = new ResponseApi{
StatusCode = HttpStatusCode.BadRequest,
Message = "Validation Error",
Response = ModelState.Values.SelectMany(x => x.Errors).Select(x =>
x.ErrorMessage)
};
then just return the response or create a validation filter to handle validations globally.
/// <summary>
/// Validatation filter to validate all the models.
/// </summary>
public class ValidationActionFilter : ActionFilterAttribute
{
/// <inheritdoc/>
public override void OnActionExecuting(HttpActionContext actionContext)
{
ModelStateDictionary modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
actionContext.Response = SendResponse(new ResponseApi
{
StatusCode= 400,
Message = "Validation Error",
Response = modelState.Values.SelectMany(x =>
x.Errors).Select(x => x.ErrorMessage)
});
}
}
private HttpResponseMessage SendResponse(ResponseApiresponse)
{
var responseMessage = new HttpResponseMessage
{
StatusCode = (HttpStatusCode)response.StatusCode,
Content = new StringContent(JsonConvert.SerializeObject(response)),
};
responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return responseMessage;
}
}

What is the best possible way to send custom error responses in .net core web api

I'm making a .net Core WebApi using .Net Core 2.2. The API is ready but the failure message and response is where I'm stuck at.
Right now, I'm getting respose like below
json
{
"empId":1999,
"empName":"Conroy, Deborah",
"enrollmentStatus":true,
"primaryFingerprintScore":65,
"secondaryFingerprintScore":60,
"primaryFingerprint":null,
"secondaryFingerprint":null,
"primaryFingerprintType":null,
"secondaryFingerprintType":null}
}
I created a json formatter class and wrote the below code
public class SuperJsonOutputFormatter : JsonOutputFormatter
{
public SuperJsonOutputFormatter(
JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool) : base(serializerSettings, charPool)
{
}
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (selectedEncoding == null)
throw new ArgumentNullException(nameof(selectedEncoding));
using (TextWriter writer =
context.WriterFactory(
context.HttpContext.Response.Body,
selectedEncoding))
{
var rewrittenValue = new
{
resultCode = context.HttpContext.Response.StatusCode,
resultMessage =
((HttpStatusCode)context.HttpContext.Response.StatusCode)
.ToString(),
result = context.Object
};
this.WriteObject(writer, rewrittenValue);
await writer.FlushAsync();
}
}
I expect all the error codes to be sent as generic error messages like the JSON below.
FOR STATUS OKAY:
{
"status" : True,
"error" : null,
"data" : {
{
"empId":1999,
"empName":"Conroy, Deborah",
"enrollmentStatus":true,
"primaryFingerprintScore":65,
"secondaryFingerprintScore":60,
"primaryFingerprint":null,
"secondaryFingerprint":null,
"primaryFingerprintType":null,
"secondaryFingerprintType":null}
}
}
}
FOR OTHER STATUS LIKE 404, 500, 400, 204
{
"status" : False,
"error" : {
"error code" : 404,
"error description" : Not Found
},
"data" : null
}
I expect all the error codes to be sent as generic error messages like the JSON below
You're almost there. What you need to do is enabling your SuperJsonOutputFormatter.
A Little Change to Your Formatter
Firstly, your formatter didn't return a json with the same schema as you want. So I create a dummy class to hold the information for error code and error description:
public class ErrorDescription{
public ErrorDescription(HttpStatusCode statusCode)
{
this.Code = (int)statusCode;
this.Description = statusCode.ToString();
}
[JsonProperty("error code")]
public int Code {get;set;}
[JsonProperty("error description")]
public string Description {get;set;}
}
And change your WriteResponseBodyAsync() method as below:
...
using (TextWriter writer = context.WriterFactory(context.HttpContext.Response.Body, selectedEncoding)) {
var statusCode = context.HttpContext.Response.StatusCode;
var rewrittenValue = new {
status = IsSucceeded(statusCode),
error = IsSucceeded(statusCode) ? null : new ErrorDescription((HttpStatusCode)statusCode),
data = context.Object,
};
this.WriteObject(writer, rewrittenValue);
await writer.FlushAsync();
}
Here the IsSucceeded(statusCode) is a simple helper method that you can custom as you need:
private bool IsSucceeded(int statusCode){
// I don't think 204 indicates that's an error.
// However, you could comment out it if you like
if(statusCode >= 400 /* || statusCode==204 */ ) { return false; }
return true;
}
Enable your Formatter
Secondly, to enable your custom Formatter, you have two approaches: One way is to register it as an global Formatter, the other way is to enable it for particular Controller or Action. Personally, I believe the 2nd way is better. So I create a Action Filter to enable your formatter.
Here's an implementation of the Filter that enables your custom formatter dynamically:
public class SuperJsonOutputFormatterFilter : IAsyncActionFilter{
private readonly SuperJsonOutputFormatter _formatter;
// inject your SuperJsonOutputFormatter service
public SuperJsonOutputFormatterFilter(SuperJsonOutputFormatter formatter){
this._formatter = formatter;
}
// a helper method that provides an ObjectResult wrapper over the raw object
private ObjectResult WrapObjectResult(ActionExecutedContext context, object obj){
var wrapper = new ObjectResult(obj);
wrapper.Formatters.Add(this._formatter);
context.Result= wrapper;
return wrapper;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
ActionExecutedContext resultContext = await next();
// in case we get a 500
if(resultContext.Exception != null && ! resultContext.ExceptionHandled){
var ewrapper = this.WrapObjectResult(resultContext, new {});
ewrapper.StatusCode = (int) HttpStatusCode.InternalServerError;
resultContext.ExceptionHandled = true;
return;
}
else {
switch(resultContext.Result){
case BadRequestObjectResult b : // 400 with an object
var bwrapper=this.WrapObjectResult(resultContext,b.Value);
bwrapper.StatusCode = b.StatusCode;
break;
case NotFoundObjectResult n : // 404 with an object
var nwrapper=this.WrapObjectResult(resultContext,n.Value);
nwrapper.StatusCode = n.StatusCode;
break;
case ObjectResult o : // plain object
this.WrapObjectResult(resultContext,o.Value);
break;
case JsonResult j : // plain json
this.WrapObjectResult(resultContext,j.Value);
break;
case StatusCodeResult s: // other statusCodeResult(including NotFound,NoContent,...), you might want to custom this case
var swrapper = this.WrapObjectResult(resultContext, new {});
swrapper.StatusCode = s.StatusCode;
break;
}
}
}
}
And don't forget to register your formatter as a service :
services.AddScoped<SuperJsonOutputFormatter>();
Finally, when you want to enable your formatter, just add a [TypeFilter(typeof(SuperJsonOutputFormatterFilter))] annotation for the controller or action.
Demo
Let's create an action method for Test:
[TypeFilter(typeof(SuperJsonOutputFormatterFilter))]
public IActionResult Test(int status)
{
// test json result(200)
if(status == 200){ return Json(new { Id = 1, }); }
// test 400 object result
else if(status == 400){ return BadRequest( new {}); }
// test 404 object result
else if(status == 404){ return NotFound(new { Id = 1, }); }
// test exception
else if(status == 500){ throw new Exception("unexpected exception"); }
// test status code result
else if(status == 204){ return new StatusCodeResult(204); }
// test normal object result(200)
var raw = new ObjectResult(new XModel{
empId=1999,
empName = "Conroy, Deborah",
enrollmentStatus=true,
primaryFingerprintScore=65,
secondaryFingerprintScore=60,
primaryFingerprint = null,
secondaryFingerprint= null,
primaryFingerprintType=null,
secondaryFingerprintType=null
});
return raw;
}
Screenshot:

HttpResponseException is not handled in a webapi controller in area

I have two webapi controllers in my MVC 5 project, one in the main domain:
namespace VincConsultancy.Controllers
{
public class QuestionGroupController : ApiController
{
//...
public IEnumerable<SAQQuestionGroup> Get(int id = 0)
{
var groups = (from g in repository.SAQQuestionGroups
where g.RequirementId == id
select g).ToList();
if (groups.Count == 0)
{
string message = string.Format("Groups with Req id = {0} not found", id);
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.NotFound, message)
);
}
return groups;
}
}
}
The other one in the area domain:
namespace VincConsultancy.Areas.admin.Controllers
{
public class SAQGroupsController : ApiController
{
//...
public IEnumerable<SAQGroup> Get(int id = 0)
{
var groups = (from g in this.dbCnxt.SAQQustnGroups
where g.RequirementId == id
select g).ToList();
if (groups.Count == 0)
{
string message = string.Format("Groups with Req id = {0} not found", id);
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.NotFound, message)
);
}
return groups;
}
}
}
In both controllers, I return error messages by throwing HttpResponseException. Visual Studio pauses the execution at the line of 'throw new HttpResponseException' only in the controller in the admin area, saying an exception occurs but was not handled by user code, that doesnt affect the functionality though, i just need to press the continue button every time. However it doesnt happen to the other controller. So, I am wondering if there is some mechanism handling the exceptions in main domain automatically. The throw-exception line is not supposed to be caught by my code, how should I get rid of visual studio breaking there?