I cannot step into my custom RequiredAttribute.
I have followed this article How to: Debug .NET Framework Source
In Tools > Options > Debugging > General:
I have Enable .NET Framework source stepping ticked
I have Enable Just My Code unticked
I have created a basic example of a custom RequiredAttribute with unit test:
using System.ComponentModel.DataAnnotations;
public class CustomRequiredAttribute : RequiredAttribute
{
public bool IsValid(object value, object container)
{
if (value == null)
{
return false;
}
string str = value as string;
if (!string.IsNullOrWhiteSpace(str))
{
return true;
}
return false;
}
}
Used by this test model:
public class CustomRequiredAttributeModel
{
[CustomRequired]
public string Name { get; set; }
}
Here is the unit test (which passes the assert correctly):
[Fact]
public void custom_required_attribute_test()
{
// arrange
var model = new CustomRequiredAttributeModel();
var controller = AccountController();
// act
controller.ValidateModel(model);
// assert
Assert.False(controller.ModelState.IsValid);
}
The unit test uses this helper method:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;
public static class ModelHelper
{
public static void ValidateModel(this Controller controller, object viewModel)
{
controller.ModelState.Clear();
var validationContext = new ValidationContext(viewModel, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(viewModel, validationContext, validationResults, true);
foreach (var result in validationResults)
{
if (result.MemberNames.Any())
{
foreach (var name in result.MemberNames)
{
controller.ModelState.AddModelError(name, result.ErrorMessage);
}
}
else
{
controller.ModelState.AddModelError("", result.ErrorMessage);
}
}
}
}
In your CustomRequiredAttribute change your method to use override,
public class CustomRequiredAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
string str = value as string;
if (!string.IsNullOrWhiteSpace(str))
{
return true;
}
return false;
}
}
Related
I've created a class "BankAccount.cs" and am using xunit to test it. I'm testing whether the constructor throws an ArgumentException if it's provided with an empty string as an argument (the argument is the bank account number as a string).
My test fails because no Exception is being thrown - I can't figure out why not.
It appears that the constructor isn't even being called; if I let the constructor throw an Exception (rather than implementing this in the setter of AccountNumber), none is thrown either. Also, if I use Console.WriteLine to debug manually, nothing is written to the console.
Exceptions (Debugging) are set to default settings.
using System;
using System.Collections.Generic;
namespace Banking.Models
{
public class BankAccount : IBankAccount
{
/* NOTE: I've deleted non-relevant fields, properties and methods for brevity */
private string _accountNumber;
public string AccountNumber
{
get { return _accountNumber; }
set
{
if(value == string.Empty)
throw new ArgumentException("Accountnumber must have a value");
_accountNumber = value;
}
}
public BankAccount(string account)
{
AccountNumber = account;
Balance = Decimal.Zero;
_transactions = new List<Transaction>();
}
public override string ToString()
{
return $"{AccountNumber} - {Balance}";
}
public override bool Equals(object obj)
{
BankAccount account = obj as BankAccount;
if (account == null) return false;
return AccountNumber == account.AccountNumber;
}
public override int GetHashCode()
{
return AccountNumber?.GetHashCode() ?? 0;
}
}
}
Tests:
using System;
using Xunit;
using Banking.Models;
namespace Tests.Models
{
public class BankAccountTest : IDisposable
{
// private variables
private BankAccount _account;
private string AccountNumber { get; set; }
// Setup
public BankAccountTest()
{
AccountNumber = "123-4567890-02";
_account = new BankAccount(AccountNumber);
}
[Fact]
public void NewAccount_BalanceZero()
{
Assert.Equal(0, _account.Balance);
}
[Fact]
public void NewAccount_CorrectAccountNumber()
{
Assert.Equal(AccountNumber, _account.AccountNumber);
}
[Fact]
public void NewAccount_EmptyString_Fails()
{
Assert.Throws<ArgumentException>(
() => new BankAccount(string.Empty));
}
// TearDown
public void Dispose()
{
}
}
}
I need to implement automatic UI Tests for a Delphi Application with Visual Studio Coded UI Tests. I have already implemented the IAccessible Interface to my Delphi-Contols. It works fine and i get the AccessibleName from the Control.
Then i implemented an extension for visual studio. In this extension i have my own PropertyProvider-, ExtensionPackage- and WinControl-Class.
PropertyProvider:
namespace CUITExtension
{
public class AccessibleNamePropertyProvider : UITestPropertyProvider
{
private static Dictionary<string, UITestPropertyDescriptor> accessibleNamePropertyMap = null;
private static Dictionary<string, UITestPropertyDescriptor> AccessibleNamePropertyMap
{
get
{
if (accessibleNamePropertyMap == null)
{
UITestPropertyAttributes read = UITestPropertyAttributes.Readable
| UITestPropertyAttributes.DoNotGenerateProperties;
accessibleNamePropertyMap = new Dictionary<string, UITestPropertyDescriptor>
(StringComparer.OrdinalIgnoreCase);
accessibleNamePropertyMap.Add("AccessibleName", new UITestPropertyDescriptor(typeof(string), read));
}
return accessibleNamePropertyMap;
}
}
public override UITestPropertyDescriptor GetPropertyDescriptor(UITestControl uiTestControl, string propertyName)
{
return AccessibleNamePropertyMap[propertyName];
}
public override ICollection<string> GetPropertyNames(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
{
// the keys of the property map are the collection of property names
return AccessibleNamePropertyMap.Keys;
}
throw new NotSupportedException();
}
public override object GetPropertyValue(UITestControl uiTestControl, string propertyName)
{
if (String.Equals(propertyName, "AccessibleName", StringComparison.OrdinalIgnoreCase))
{
object[] native = uiTestControl.NativeElement as object[];
IAccessible acc = native[0] as IAccessible;
return acc.accName;
}
throw new NotSupportedException();
}
public override int GetControlSupportLevel(UITestControl uiTestControl)
{
if (string.Equals(uiTestControl.TechnologyName, "MSAA",
StringComparison.OrdinalIgnoreCase) &&
uiTestControl.ControlType.NameEquals("Custom"))
{
return (int)ControlSupport.ControlSpecificSupport;
}
// This is not my control, so return NoSupport
return (int)ControlSupport.NoSupport;
}
public override string[] GetPredefinedSearchProperties(Type specializedClass)
{
return null;
}
public override string GetPropertyForAction(UITestControl uiTestControl, UITestAction action)
{
return null;
}
public override string[] GetPropertyForControlState(UITestControl uiTestControl, ControlStates uiState, out bool[] stateValues)
{
stateValues = null;
return null;
}
public override Type GetPropertyNamesClassType(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
return typeof(AccessibleControl.PropertyNames);
return null;
}
public override Type GetSpecializedClass(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
return typeof(AccessibleControl);
return null;
}
public override void SetPropertyValue(UITestControl uiTestControl, string propertyName, object value)
{
return;
}
}
}
ExtensionPackage:
[assembly: Microsoft.VisualStudio.TestTools.UITest.Extension.UITestExtensionPackage(
"AccessibleNameExtensionPackage",
typeof(CUITExtension.AccessibleNameExtensionPackage))]
namespace CUITExtension
{
class AccessibleNameExtensionPackage : UITestExtensionPackage
{
public override string PackageDescription
{
get { return "Supports coded UI testing by using the AccessibleName"; }
}
public override string PackageName
{
get { return "AccessibleName Extension Package"; }
}
public override string PackageVendor
{
get { return "Microsoft (sample)"; }
}
public override Version PackageVersion
{
get { return new Version(1, 0); }
}
public override Version VSVersion
{
get { return new Version(14, 0); }
}
public override void Dispose() { }
public override object GetService(Type serviceType)
{
if (serviceType == typeof(UITestPropertyProvider))
{
if (propertyProvider == null)
{
propertyProvider = new AccessibleNamePropertyProvider();
}
return propertyProvider;
}
return null;
}
private UITestPropertyProvider propertyProvider = null;
}
}
WinControl:
namespace CUITExtension
{
public class AccessibleControl : WinControl
{
public AccessibleControl(UITestControl c) : base(c)
{
TechnologyName = "MSAA";
SearchProperties.Add(UITestControl.PropertyNames.ControlType, "Custom");
}
public virtual string AccessibleName
{
get
{
return (string)GetProperty("AccessibleName");
}
}
}
}
Now the Coded UI Test Builder is showing the AccessibleName and is also generating AccessibleName as a SearchProperty.
UIMap:
public AccessibleControl UIItemCustom
{
get
{
if ((this.mUIItemCustom == null))
{
this.mUIItemCustom = new AccessibleControl(this);
#region Search Criteria
this.mUIItemCustom.SearchProperties["AccessibleName"] = "UniqueName1";
this.mUIItemCustom.SearchProperties[WinControl.PropertyNames.ClassName] = "TEdit";
this.mUIItemCustom.WindowTitles.Add("Title");
#endregion
}
return this.mUIItemCustom;
}
}
*I have changed the Searchproperties here (only for the post, i didnt changed the generated code)
Now when I start the test, I get an exception that says that AccessibleName is not an valid searchproperty. I got this exception before, when i havent implemented the extension yet. But I thougth by implementing the propertyprovider AccessibleName should be a valid searchproperty now.
I tried to debug it, but it seems like by searching the Control it doesnt use the propertyprovider and i have no idea why?
I hope you can help me and if you need more information just ask.
Paul
I got the problem with the valid searchproperty to work.
I overrode the GetValidSearchProperties method from WinControl.
protected override Dictionary<string, bool> GetValidSearchProperties()
{
Dictionary<string, bool> searchProperties = base.GetValidSearchProperties();
if (!searchProperties.ContainsKey("AccessibleName"))
searchProperties.Add("AccessibleName", true);
return searchProperties;
}
I need implement a cusotm attribute then using asp.net data annotation to validate a class. Unfortunately, the attribute class is not called at run time. Please help me out. Many thanks. Below is the source code.
using System;
using System.Collections.Generic;
using CaseMgr.Model.Base;
using System.Linq;
using System.ComponentModel.DataAnnotations;
namespace CaseMgr.Model.BusinessObjects
{
public partial class PatLiverException : BusinessBase<decimal>, IComparable<PatLiverException>, IEquatable<PatLiverException>
{
private LiverExcepDisease _liverExcepDisease = null;
private DateTime _sccApprovalDate = new DateTime();
public PatLiverException() { }
public virtual LiverExcepDisease LiverExcepDisease
{
get { return _liverExcepDisease; }
set { _liverExcepDisease = value; }
}
[SccApprovalDateValidate("SccApprovalDate", "LiverExcepDisease")]
public virtual DateTime SccApprovalDate
{
get { return _sccApprovalDate; }
set { _sccApprovalDate = value; }
}
}
public class SccApprovalDateValidateAttribute : ValidationAttribute
{
public string m_SccApprovalDate { get; private set; }
public string m_LiverExcepDisease { get; private set; }
public SccApprovalDateValidateAttribute(string SccApprovalDate_PropertyName, string LiverExcepDisease_PropertyName)
{
this.m_SccApprovalDate = SccApprovalDate_PropertyName;
this.m_LiverExcepDisease = LiverExcepDisease_PropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var SccApprovalDate_Property = context.ObjectType.GetProperty(m_SccApprovalDate);
DateTime SccApprovalDate_Value = (DateTime)SccApprovalDate_Property.GetValue(context.ObjectInstance, null);
var LiverExcepDisease_Property = context.ObjectType.GetProperty(m_LiverExcepDisease);
LiverExcepDisease LiverExcepDisease_Value = (LiverExcepDisease)LiverExcepDisease_Property.GetValue(context.ObjectInstance, null);
if (SccApprovalDate_Value != null && SccApprovalDate_Value != DateTime.MinValue && SccApprovalDate_Value != DateTime.MaxValue)
{
return LiverExcepDisease_Value.Id == 10 ? ValidationResult.Success : new ValidationResult("When other, SccApprovalDate can not be null.");
}
else
{
return ValidationResult.Success;
}
}
}
}
If you are going to use the validation attribute on a class you need:
[AttributeUsage(AttributeTargets.Class)]
public class Sccxxxxxxxxx : ValidationAttribute
https://msdn.microsoft.com/en-us/library/tw5zxet9.aspx?f=255&MSPPError=-2147217396
Take a look at this post: ASP.NET MVC: Custom Validation by DataAnnotation
Also, if you put a breakpoint inside SccApprovalDateValidateAttribute.ValidationResult() does it get hit?
Your code looks okay, except I cannot see where you call or use the property,SccApprovalDate, because your validation attribute declared on the property will only be invoked if the property is called or used by the run-time. Can you post how you use the property SccApprovalDate?
Is it possible to test a FluentValidation PropertyValidator in isolation?
I know I can test the Validator that's using the PropertyValidator for specific errors but I’d rather test true/false just on the property validator if possible.
Can this be done? If so, how?
I also wanted to test my true / false logic. It is a shame the IsValid method is protected. My work around was to create another IsValid method and have the protected IsValid call through to it.
public class MyValidator: PropertyValidator
{
public MyValidator(
string errorMessage = "default Message") : base(errorMessage)
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var stringToValidate = context.PropertyValue as String;
return IsValid(stringToValidate);
}
public bool IsValid(string stringToValidate)
{
if (stringToValidate == null)
{
return false;
}
//testing logic here
return true;
}
}
I know this has been a while, but I achieved this as follows:
Custom Validator:
public class MyValidator : PropertyValidator
{
public MyValidator ()
: base("Value must be null or between 0 and 3.")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
if (context.PropertyValue == null)
{
return true;
}
var value = (decimal)context.PropertyValue;
return value >= 0m && value <= 3m;
}
}
Test Validator:
public class TestValidator : InlineValidator<TestObject>
{
public TestValidator (params Action<TestValidator >[] actions)
{
foreach (var action in actions)
{
action(this);
}
}
}
Test Object:
public class TestObject
{
public TestObject(decimal? val)
{
this.GenericDecimal = val;
}
public decimal? GenericDecimal { get; set; }
}
Test:
[Test]
public void TestIt()
{
var validator = new TestValidator(v => v.RuleFor(obj => obj.GenericDecimal).SetValidator( new MyValidator() ));
Assert.IsTrue(validator.Validate(new TestObject(null)).IsValid);
Assert.IsTrue(validator.Validate(new TestObject(0m)).IsValid);
Assert.IsTrue(validator.Validate(new TestObject(3m)).IsValid);
Assert.IsFalse(validator.Validate(new TestObject(-1m)).IsValid);
Assert.IsFalse(validator.Validate(new TestObject(3.01m)).IsValid);
}
As for version 6.2 of FluentValidation it is possible to build the PropertyValidator.Validate() parameter due to making ValidatorSelectors globally configurable: https://github.com/JeremySkinner/FluentValidation/commit/95376c0519da1a06388be91a97fb5062fd4a162e
In the below example you see how I validate the 'puic' property of Track
Unit test:
public void ExistsInCollectionValidatorTest()
{
var track = new Track()
{
puic = "p1"
};
var sut = new ExistsInCollectionValidator<Track>();
// Build PropertyValidator.Validate() parameter
var selector = ValidatorOptions.ValidatorSelectors.DefaultValidatorSelectorFactory();
var context = new ValidationContext(track, new PropertyChain(), selector);
var propertyValidatorContext = new PropertyValidatorContext(context, PropertyRule.Create<Track,string>(t => t.puic), "puic");
var results = sut.Validate(propertyValidatorContext);
// Assertion..
}
I have API where I need to validate my user model. I choose an approach where I create different classes for Create/Edit actions to avoid mass-assignment and divide validation and actual model apart.
I don't know why but ModelState.IsValid returns true even when it should not. Am I doing something wrong?
Controller
public HttpResponseMessage Post(UserCreate user)
{
if (ModelState.IsValid) // It's valid even when user = null
{
var newUser = new User
{
Username = user.Username,
Password = user.Password,
Name = user.Name
};
_db.Users.Add(newUser);
_db.SaveChanges();
return Request.CreateResponse(HttpStatusCode.Created, new { newUser.Id, newUser.Username, newUser.Name });
}
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
Model
public class UserCreate
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string Name { get; set; }
}
Debug proof
The ModelState.IsValid internally checks the Values.All(modelState => modelState.Errors.Count == 0) expression.
Because there was no input the Values collection will be empty so ModelState.IsValid will be true.
So you need to explicitly handle this case with:
if (user != null && ModelState.IsValid)
{
}
Whether this is a good or bad design decision that if you validate nothing it will true is a different question...
Here is an action filter to check for null models or invalid models. (so you dont have to write the check on every action)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace Studio.Lms.TrackingServices.Filters
{
public class ValidateViewModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.Any(kv => kv.Value == null)) {
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Arguments cannot be null");
}
if (actionContext.ModelState.IsValid == false) {
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
}
You can register it globally:
config.Filters.Add(new ValidateViewModelAttribute());
Or use it on demand on classes/actions
[ValidateViewModel]
public class UsersController : ApiController
{ ...
I wrote a custom filter which not only ensures that all non optional object properties are passed, but also checks if model state is valid:
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public sealed class ValidateModelAttribute : ActionFilterAttribute
{
private static readonly ConcurrentDictionary<HttpActionDescriptor, IList<string>> NotNullParameterNames =
new ConcurrentDictionary<HttpActionDescriptor, IList<string>> ();
/// <summary>
/// Occurs before the action method is invoked.
/// </summary>
/// <param name="actionContext">The action context.</param>
public override void OnActionExecuting (HttpActionContext actionContext)
{
var not_null_parameter_names = GetNotNullParameterNames (actionContext);
foreach (var not_null_parameter_name in not_null_parameter_names)
{
object value;
if (!actionContext.ActionArguments.TryGetValue (not_null_parameter_name, out value) || value == null)
actionContext.ModelState.AddModelError (not_null_parameter_name, "Parameter \"" + not_null_parameter_name + "\" was not specified.");
}
if (actionContext.ModelState.IsValid == false)
actionContext.Response = actionContext.Request.CreateErrorResponse (HttpStatusCode.BadRequest, actionContext.ModelState);
}
private static IList<string> GetNotNullParameterNames (HttpActionContext actionContext)
{
var result = NotNullParameterNames.GetOrAdd (actionContext.ActionDescriptor,
descriptor => descriptor.GetParameters ()
.Where (p => !p.IsOptional && p.DefaultValue == null &&
!p.ParameterType.IsValueType &&
p.ParameterType != typeof (string))
.Select (p => p.ParameterName)
.ToList ());
return result;
}
}
And I put it in global filter for all Web API actions:
config.Filters.Add (new ValidateModelAttribute ());
Updated slightly for asp.net core...
[AttributeUsage(AttributeTargets.Method)]
public sealed class CheckRequiredModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var requiredParameters = context.ActionDescriptor.Parameters.Where(
p => ((ControllerParameterDescriptor)p).ParameterInfo.GetCustomAttribute<RequiredModelAttribute>() != null).Select(p => p.Name);
foreach (var argument in context.ActionArguments.Where(a => requiredParameters.Contains(a.Key, StringComparer.Ordinal)))
{
if (argument.Value == null)
{
context.ModelState.AddModelError(argument.Key, $"The argument '{argument.Key}' cannot be null.");
}
}
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
context.Result = new BadRequestObjectResult(errors);
return;
}
base.OnActionExecuting(context);
}
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class RequiredModelAttribute : Attribute
{
}
services.AddMvc(options =>
{
options.Filters.Add(typeof(CheckRequiredModelAttribute));
});
public async Task<IActionResult> CreateAsync([FromBody][RequiredModel]RequestModel request, CancellationToken cancellationToken)
{
//...
}
This happened to me, and in my case, I had to change using Microsoft.Build.Framework; to using System.ComponentModel.DataAnnotations; (and add the reference).
I was looking for a solution to this problem and came out here first. After some further research I have realized the following solution:
How do you use my solution?
You can register it globally:
config.Filters.Add(new ValidateModelStateAttribute());
Or use it on demand for a class
[ValidateModelState]
public class UsersController : ApiController
{...
or for a methode
[ValidateModelState]
public IHttpActionResult Create([Required] UserModel data)
{...
As you can see, a [System.ComponentModel.DataAnnotations.Required] atribute has been placed in the method parameter.
This indicates that the model is required and can not be null.
You can also use with a custom message:
[ValidateModelState]
public IHttpActionResult Create([Required(ErrorMessage = "Custom message")] UserModel data)
{...
Here is my code:
using System;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace your_base_namespace.Web.Http.Filters
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
public class ValidateModelStateAttribute : ActionFilterAttribute
{
private delegate void ValidateHandler(HttpActionContext actionContext);
private static readonly ConcurrentDictionary<HttpActionBinding, ValidateHandler> _validateActionByActionBinding;
static ValidateModelStateAttribute()
{
_validateActionByActionBinding = new ConcurrentDictionary<HttpActionBinding, ValidateHandler>();
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
GetValidateHandler(actionContext.ActionDescriptor.ActionBinding)(actionContext);
if (actionContext.ModelState.IsValid)
return;
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
private ValidateHandler GetValidateHandler(HttpActionBinding actionBinding)
{
ValidateHandler validateAction;
if (!_validateActionByActionBinding.TryGetValue(actionBinding, out validateAction))
_validateActionByActionBinding.TryAdd(actionBinding, validateAction = CreateValidateHandler(actionBinding));
return validateAction;
}
private ValidateHandler CreateValidateHandler(HttpActionBinding actionBinding)
{
ValidateHandler handler = new ValidateHandler(c => { });
var parameters = actionBinding.ParameterBindings;
for (int i = 0; i < parameters.Length; i++)
{
var parameterDescriptor = (ReflectedHttpParameterDescriptor)parameters[i].Descriptor;
var attribute = parameterDescriptor.ParameterInfo.GetCustomAttribute<RequiredAttribute>(true);
if (attribute != null)
handler += CreateValidateHandler(attribute, parameterDescriptor.ParameterName);
}
return handler;
}
private static ValidateHandler CreateValidateHandler(ValidationAttribute attribute, string name)
{
return CreateValidateHandler(attribute, new ValidationContext(new object()) { MemberName = name });
}
private static ValidateHandler CreateValidateHandler(ValidationAttribute attribute, ValidationContext context)
{
return new ValidateHandler(actionContext =>
{
object value;
actionContext.ActionArguments.TryGetValue(context.MemberName, out value);
var validationResult = attribute.GetValidationResult(value, context);
if (validationResult != null)
actionContext.ModelState.AddModelError(context.MemberName, validationResult.ErrorMessage);
});
}
}
}
There is a simple Solution for your problem
public class UserCreate
{
[Required(AllowEmptyStrings = false)]
public string Username { get; set; }
}
Here AllowEmptyStrings = false can be used for your validation
Try
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
in the startup.cs file's ConfigureServices()
What I did was to create an Attribute along with an ActionFilter and a Extension Method to avoid null models.
The extension method looks for parameters with the NotNull attribute and check if they are null, if true, they are instantiated and set in the ActionArguments property.
This solution can be found here: https://gist.github.com/arielmoraes/63a39a758026b47483c405b77c3e96b9
This "ModelState.IsValid returns true even when it should not" problem can also appear if you forgot to add getters and setters on your model (OP didn't forget, but I did which led me to this question). I hope it's ok to provide solutions that have the same symptoms but are slightly different than OP's code:
Wrong:
public class UserRegisterModel
{
[Required]
public string Login; // WRONG
[Required]
public string Password; // WRONG
}
Good:
public class UserRegisterModel
{
[Required]
public string Login { get; set; }
[Required]
public string Password { get; set; }
}
this issue happened to me .i do not know why but take it easy just change your action Object name(UserCreate User) by some other like (UserCreate User_create)