ModelState.IsValid always evaluating as true when working with FluentValidation - c#

I am a junior programmer trying to write a simple little bit of code to test out FluentValidation, but without manually calling the validator and adding the results to the modelstate with .AddToModelState, I cannot get the ModelState.IsValid to recognize there are errors in the validation. Am I missing integration somewhere?
This is my Value Model, just a string array with two preset values.
using FluentValidation.Attributes;
using Playground2.Validators;
namespace Playground2.Models
{
[Validator(typeof(ValueValidator))]
public class Value
{
public string[] values = { "value1", "" };
}
}
This is my Validator, looking for two values between 5 and 10 characters.
using FluentValidation;
using Playground2.Models;
namespace Playground2.Validators
{
public class ValueValidator : AbstractValidator<Value>
{
public ValueValidator()
{
RuleFor(x => x.values[0]).Length(5, 10);
RuleFor(x => x.values[1]).Length(5, 10);
}
}
}
In the ValuesController, I am simply creating a value object and running a check to see if it passes validation before being output.
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using Playground2.Models;
using System.Collections.Generic;
namespace Playground2.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
var value = new Value();
if (!ModelState.IsValid)
{
return new string[] { "Not valid" };
}
else
{
return value.values;
}
}
But when run, the ModelState.IsValid is always evaluating as true, though the information fed into values is by default invalid.

FluentValidation follows MVC's/HTML's convention in regard to GETs and POSTs. In this case it's not expecting any validation to be done an a page's initial GET as a user wouldn't necessarily have performed any action. They're instead requesting the page to start doing something - they haven't gotten around to supplying data.
Once the user fills out information the convention is to submit the data in a HTML <form> using a <button> or <input type="submit"/> to submit the data to the controller via a HttpPost marked method. At this point validation has triggered and you'll be able to correctly interrogate the ModelState.IsValid.

Related

Why returning null does nothing in web api project?

I'm new to asp.net core 3, sorry if my question sounds too basic, below is my controller class:
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase {
List<string> _fruit = new List<string> { "Pear", "Lemon", "Peach" };
[HttpGet("fruit")]
public IEnumerable<string> MethodXXX() {
return _fruit;
}
...
}
so when I route to my/fruit, I get a list of string.
But if I change the MethodXXX to :
[HttpGet("fruit")]
public IEnumerable<string> MethodXXX() {
return null;
}
Then the browser always fall back to the previous url, e.g I'm on my/other in the beginning, then I change the url to my/fruit, I can see that the browser sends a new request and the url changes to my/fruit for a short period of time then it fall back to my/other again.
What is this behavior for? I am pretty sure I read from a book which says that you can return null from action method?
This is a behavior newly introduced in .Net Core 3 where if the Action method returns null, it just sends a 204 response which represents No Content. If you desire to return null from an action method, you can override this behavior by using below code block inside Startup.cs file which removes the corressponding Output formatter and you will get null returned from the API response.
services.AddControllers(options =>
{
options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
});
you most changed
[HttpGet]
[ActionName("fruit")]
public IEnumerable<string> MethodXXX() {
return null;
}

Can't bind json in POST request body to class argument in .net-core

I'm trying to make a post request from my Angular frontend to the .net Core 3.1 backend, but in the controller method the argument object only gets by default 0 and null values;
let response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(obj)
});
[ApiController]
[Route("[controller]")]
[ApiExplorerSettings(IgnoreApi = true)]
public class RequestController : ControllerBase
{
[HttpPost]
public async Task<IAResponseViewModel> PostAnswer([FromBody] IAResponseViewModel something)
{
var temp = something.ToString();
if (!ModelState.IsValid)
{
Console.WriteLine();
}
return something;
}
}
public class IAResponseViewModel
{
public string AdministrationId { get; }
public int RoundUsesItemId { get; }
public int ResponseOptionId { get; }
}
The JSON object I see being submitted
{AdministrationId: "12345678-00e8-4edb-898b-03ee7ff517bf", RoundUsesItemId: 527, ResponseOptionId: 41}
When inspecting the controller method the 3 values of the IAResponseViewModel are null or 0
When I change the argument to object I get something with a value of
ValueKind = Object : "{"AdministrationId":"12345678-00e8-4edb-898b-03ee7ff517bf","RoundUsesItemId":523,"ResponseOptionId":35}"
I've tried with and without the [FromBody] attribute, changing the casing of the properties of the controller method's argument and the frontend argument, copy pasting the viewmodel attributes on to the submitted object keys, wrapping the posted object in a 'something' object. the ModelState.IsValid attribute shows as true.
I've red other answers such as Asp.net core MVC post parameter always null &
https://github.com/dotnet/aspnetcore/issues/2202 and others but couldn't find an answer that helped.
Why isn't the model binding working and how do I populate the viewmodel class I'm using with the json data?
From a comment on my original question:
Could it be because the properties in IAResponseViewModel are 'get only'?
Indeed this was the problem. Giving the properties (default) set methods fixed the problem.

Razor equivalent of FromQuery attribute

I'm trying to build a simple page with a simple form with ASP.NET using a Razor page, and can't work out how to handle a particular post-back. In particular (through factors outside of my control) I'm getting a post-back with a single query parameter that is lowercase and kebab-case, in a regular MVC page I could use the FromQuery attribute, but it doesn't appear to work in this instance as with or without the attribute I get null passed to OnPostAsync each time. An example to illustrate this issue follows:
Example.cshtml
#page
#model my_namespace.Pages.ExampleModel
#{
ViewData["Title"] = "Example Title";
}
<h2>Example</h2>
<form method="post">
<!--- In actual code I don't have control of the name, so this is for illustrative purposes. --->
<input type="text" name="kebabbed-name"/>
<input type="submit" />
</form>
Example.cshtml.cs
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace my_namespace.Pages
{
public class ExampleModel : PageModel
{
public async Task<IActionResult> OnGetAsync()
{
return Page();
}
// This method is hit as expected, but the parameter is always null.
// Changing the input name to something like "sample" and this parameter to match works however.
public async Task<IActionResult> OnPostAsync(string kebabbedName)
{
// Handling of the Post request
return Page();
}
}
So what I'm looking for is some way to process the postback with that kebabbed-name as a parameter - any solution would be welcomed.
It doesn't seem like Razor pages could process kebabbed-names automagically, however you can create a property in your PageModel class with a custom name that should bind to the postback value.
// For GET request
// [BindProperty(Name = "kebabbed-name", SupportsGet = true)]
// For POST request
[BindProperty(Name = "kebabbed-name")]
public string kebabbedName { get; set; }

Issue with Model Binding on POST in API

I am having trouble understanding the model binding process in Asp.Net core 2. I have a very simple API that has a model. It has some basic validation on it. Whenever a user posts an incorrect model, I am trying to return a 422 unprocessableentity along with the error messages from the modelstate.
The 2 issues I am trying to understand are as follows:
If I post a request without an ID, a default ID of 0 is being created circumventing the required attribute. I am assuming this is C# functionality for providing default values to fields. Is there a way to circumvent this?
The other problem is that if I place a breakpoint in my post action and send a bad request, it does not even go into the method. It sends back a 400 bad request by using the validation attributes. How does this work? Does the request halt as soon as it tries to model bind to an invalid property (i.e. Name length > 10)? What I need it to do is send back a 422 unprocessable entity with the same error message instead of 400.
Does ASP.NET not even go into the method if the model state validation fails based on the validation attributes? What would be a better way to solve this issue to return a 422 error code?
Below is the code for my various classes (I used the API template when creating the project):
Startup.cs - Only thing I added here was the singleton instance of my in-memory context
public void ConfigureServices(IServiceCollection services)
{
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc();
services.AddSingleton<IItemRepository, ItemRepository>();
}
IItemRepository.cs - My interface for DI
public interface IItemRepository
{
List<ItemModel> Items { get; set; }
void AddValue(ItemModel itemModel);
}
ItemRepository.cs - Concrete implementation
public class ItemRepository : IItemRepository
{
public List<ItemModel> Items { get; set; } = new List<ItemModel>();
public ItemRepository()
{
Items.AddRange(
new List<ItemModel> {
new ItemModel {Id = 1, Name = "Test1" },
new ItemModel {Id = 2, Name = "Test2" }
}
);
}
public void AddValue(ItemModel itemModel)
{
Items.Add(itemModel);
}
}
ItemModel.cs - My model class for user input
public class ItemModel
{
[Required]
public int Id { get; set; }
[MaxLength(10)]
public string Name { get; set; }
}
ValuesController.cs
[Route("api/[controller]")]
[ApiController]
public class ValuesController : Controller
{
private IItemRepository _context;
public ValuesController(IItemRepository context)
{
_context = context;
}
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return Ok(_context.Items);
}
// GET api/values/5
[HttpGet("{id}", Name = "GetSingle")]
public ActionResult<string> Get(int id)
{
return Ok(_context.Items.Where(x => x.Id == id));
}
// Problem here - placing a breakpoint in below method does not do anytthing as it will return a 400 bad request instead of 422
[HttpPost]
public ActionResult Post([FromBody] ItemModel itemModel)
{
if (!ModelState.IsValid)
{
return new UnprocessableEntityObjectResult(ModelState);
}
ItemModel addNew = new ItemModel { Id = itemModel.Id, Name = itemModel.Name };
_context.AddValue(addNew);
return Ok(addNew);
}
}
For your first issue, if you don't want to make the property nullable, you can also put a range attribute [Range(1, int.MaxValue)], but 0 will not be a valid value in this case.
For your second issue, if you still want the automatic model validation from ApiControllerAttribute but want a 422 response code instead of 400 you can use the InvalidModelStateResponseFactory configuration option.
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = ctx =>
new UnprocessableEntityObjectResult(ctx.ModelState);
});
If I post a request without an ID, a default ID of 0 is being created
circumventing the required attribute. I am assuming this is C#
functionality for providing default values to fields. Is there a way
to circumvent this?
As #StephenMuecke answered here, you need to change your model to
public class ItemModel
{
[Required]
public int? Id { get; set; }
[MaxLength(10)]
public string Name { get; set; }
}
The other problem is that if I place a breakpoint in my post action
and send a bad request, it does not even go into the method. It sends
back a 400 bad request by using the validation attributes. How does
this work? Does the request halt as soon as it tries to model bind to
an invalid property (i.e. Name length > 10)? What I need it to do is
send back a 422 unprocessable entity with the same error message
instead of 400.
This is because you applied the ApiControllerAttribute to the Controller. From the documentation:
Validation errors automatically trigger an HTTP 400 response. The following code becomes unnecessary in your actions:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
You can either remove the attribute, or, as the same link explains, add this to the startup configuration:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
})
Your first issue can be solved by making the property nullable. As commented by Stepen Muecke.
Also take a look here, perhaps the BindRequired attribute can help. The article also describes how to tweak behaviour.
For your second issue, this is new (breaking) behaviour by Asp.Net Core 2.1. New is the automatic 400 response. That explains why your breakpoint isn't hit. You can suppress this as follows:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});

How to determine Route Prefix programmatically in asp.net mvc?

I wanted to provide some URL separation for my public/anonymous controllers and views from the admin/authenticated controllers and views. So I ended up using entirely Attribute Routing in order to take more control of my URLs. I wanted my public URLs to start with "~/Admin/etc." while my public URLs would not have any such prefix.
Public Controller (one of several)
[RoutePrefix("Home")]
public class HomeController : Controller
{
[Route("Index")]
public ActionResult Index()
{ //etc. }
}
Admin Controller (one of several)
[RoutePrefix("Admin/People")]
public class PeopleController : Controller
{
[Route("Index")]
public ActionResult Index()
{ //etc. }
}
This allows me to have public URLs such as:
http://myapp/home/someaction
...and admin/authenticated URLs such as:
http://myapp/admin/people/someaction
But now I want to do some dynamic stuff in the views based on whether the user is in the Admin section or the Public section of the site. How can I access this programmatically, properly?
I know I could do something like
if (Request.Url.LocalPath.StartsWith("/Admin"))
...but it feels "hacky." I know I can access the controller and action names via
HttpContext.Current.Request.RequestContext.RouteData.Values
...but the "admin" piece isn't reflected in there, because it's just a route prefix, not an actual controller name.
So, the basic question is, how do I programmatically determine whether the currently loaded view is under the "admin" section or not?
You just need to reflect the RoutePrefixAttribute from the Controller type, and then get its Prefix value. The Controller instance is available on the ViewContext.
This example creates a handy HTML helper that wraps all of the steps into a single call.
using System;
using System.Web.Mvc;
public static class RouteHtmlHelpers
{
public static string GetRoutePrefix(this HtmlHelper helper)
{
// Get the controller type
var controllerType = helper.ViewContext.Controller.GetType();
// Get the RoutePrefix Attribute
var routePrefixAttribute = (RoutePrefixAttribute)Attribute.GetCustomAttribute(
controllerType, typeof(RoutePrefixAttribute));
// Return the prefix that is defined
return routePrefixAttribute.Prefix;
}
}
Then in your view, you just need to call the extension method to get the value of the RoutePrefixAttribute.
#Html.GetRoutePrefix() // Returns "Admin/People"

Categories

Resources