Manually validate Model in Web api controller - asp.net

I have a class called 'User' and a property 'Name'
public class User
{
[Required]
public string Name { get; set; }
}
And api controller method is
public IHttpActionResult PostUser()
{
User u = new User();
u.Name = null;
if (!ModelState.IsValid)
return BadRequest(ModelState);
return Ok(u);
}
How do i manually validate the User object so the ModelState.IsValid return false to me?

You can use the Validate() method of the ApiController class to manually validate the model and set the ModelState.
public IHttpActionResult PostUser()
{
User u = new User();
u.Name = null;
this.Validate(u);
if (!ModelState.IsValid)
return BadRequest(ModelState);
return Ok(u);
}

This answer is not for this case, but it is very relevant if you want to validate a parameter manually:
public IHttpActionResult Post(User user)
{
ModelState.Clear(); // remove validation of 'user'
// validation is done automatically when the action
// starts the execution
// apply some modifications ...
Validate(user); // it adds new keys to 'ModelState', it does not update any keys
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// ...
}

You will need to define custom Validation Attribute as
class CustomValidatorAttribute : ValidationAttribute
{
//custom message in ctor
public CustomValidator() : base("My custom message") {}
public CustomValidator(string Message) : base(Message) {}
public override bool IsValid(object value)
{
return !string.IsNullOrWhiteSpace(value.ToString());
}
//return a overriden ValidationResult
protected override ValidationResult IsValid(Object value,ValidationContext validationContext)
{
if (IsValid(value)) return ValidationResult.Success;
var message = "ohoh";
return new ValidationResult(message);
}
}
likewise in your model class
public class User
{
[Required]
[CustomValidator("error")]
public string Name { get; set; }
}

The model should be an input parameter to your ActionMethod, and ModelState.IsValid will validate as per the attributes you set in the Model class, in this case as it is set [Required] it will be validated againg null values,
and if you just wish to manually check whether there is a value, you can check it directly.
if (user.Name == null) {
return;
}

Related

Combine [FromBody] with [FromHeader] in WebAPI in .net Core 3.0

we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).

ASP MVC Data Annotation - How to verify that two model properties are different using CompareAttribute

I regularly use the CompareAttribute data annotation in my models to validate that two fields are equal. For example, most of us use it to compare a password and a confirm password field.
It may seems trivial but I wonder how to use such annotation to compare that two fields are differents. For instance, I would like to verify that the password is different from the username.
For more complex validations, I know I have to use custom validators, but I was just wondering if there was something built-in for that.
Thank you.
You have two choices here, create your own ValidationAttribute inheriting from CompareAttribute or inheriting from ValidationAttribute.
1) Custom ValidationAttribute inherit from CompareAttribute
public class NotEqualAttribute : CompareAttribute
{
public string BasePropertyName { get; private set; }
private const string DefaultErrorMessage = "'{0}' and '{1}' must not be the same.";
public MyCustomCompareAttribut(string otherProperty)
: base(otherProperty)
{
BasePropertyName = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(DefaultErrorMessage, name, BasePropertyName);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var result = base.IsValid(value, validationContext);
if (result == null)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
2)Custom ValidationAttribute inherit from ValidationAttribute
public class NotEqualAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "'{0}' and '{1}' must not be the same.";
public string BasePropertyName { get; private set; }
public NotEqualAttribute(string basePropertyName)
: base(DefaultErrorMessage)
{
BasePropertyName = basePropertyName;
}
public override string FormatErrorMessage(string name)
{
return string.Format(DefaultErrorMessage, name, BasePropertyName);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(BasePropertyName);
if (property == null)
{
return new ValidationResult(
string.Format(
CultureInfo.CurrentCulture, "{0} is invalid property", BasePropertyName
)
);
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
if (object.Equals(value, otherValue))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
Then you can use either one like:
public class YourModelClass
{
public string PropertyA{ get; set; }
[NotEqual("PropertyA")]
public string PropertyB{ get; set; }
}

ASP.NET MVC - Choose which validation annotations to use

I have a model with properties that look like this:
public class YourDetails {
[Required(ErrorMessage = "Code is required")]
[StringLength(10, ErrorMessage = "Code length is wrong", MinimumLength = 2)]
[Range(0, int.MaxValue, ErrorMessage = "Please enter a value bigger than {1}")]
public int Code { get; set; }
}
The UI validation is setup the usual out of the box way with unobtrusive JS validation plugin.
The issue: I have 2 navigation actions, back and next. Next is fine, validation fires when things are wrong, and when things are right i.e. .isValid() returns true, the data is passed to the DB service etc etc.
However when I press 'back' I have a requirement to validate the form/ViewModel differently prior to saving. I.e. make sure Code is a positive integer, but don't bother with the Required or StringLength validation.
So basically I want to validate fully on Next but partially on Back. Is that possible?
When I've done something similar in the past the easiest way i found was to use fluent validation http://fluentvalidation.codeplex.com/wikipage?title=mvc. You can pass parameters to the validator and switch to different rule sets.
I've used the following conditional "Required" & "StringLength" attributes in the past and they work well.
Required If Attribute:
using System;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace Website.Core.Mvc.DataAnnotations
{
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public class RequiredIfAttribute : RequiredAttribute
{
public string OtherProperty { get; set; }
public object OtherPropertyValue { get; set; }
public RequiredIfAttribute(string otherProperty, object value)
: base()
{
OtherProperty = otherProperty;
OtherPropertyValue = value;
}
private object _TypeId = new object();
public override object TypeId
{
get
{
return _TypeId;
}
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo property = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (property == null)
{
return new ValidationResult(this.OtherProperty + " not found");
}
// Get
object actualOtherPropertyValue = property.GetValue(validationContext.ObjectInstance, null);
// If the other property matches the expected value then validate as normal
if (IsRequired(OtherPropertyValue, actualOtherPropertyValue))
{
// Call base and validate required as normal
ValidationResult isValid = base.IsValid(value, validationContext);
return isValid;
}
return ValidationResult.Success;
}
protected virtual bool IsRequired(object otherPropertyValue, object actualOtherPropertyValue)
{
return object.Equals(OtherPropertyValue, actualOtherPropertyValue);
}
}
}
String Length If Attribute:
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace Website.Core.Mvc.DataAnnotations
{
public class StringLengthIfAttribute : StringLengthAttribute
{
public string OtherProperty { get; set; }
public object OtherPropertyValue { get; set; }
public StringLengthIfAttribute(int maximumLength, string otherProperty, object value)
: base(maximumLength)
{
OtherProperty = otherProperty;
OtherPropertyValue = value;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo property = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (property == null)
{
return new ValidationResult(this.OtherProperty + " not found");
}
// Get
object actualOtherPropertyValue = property.GetValue(validationContext.ObjectInstance, null);
// If the other property matches the expected value then validate as normal
if (object.Equals(OtherPropertyValue, actualOtherPropertyValue))
{
// Call base and validate required as normal
return base.IsValid(value, validationContext);
}
return null;
}
}
}
Example Usage:
public class MyModel
{
[RequiredIf("IsBack", false)]
public string Name { get; set; }
public bool IsBack { get; set; }
}

How to add the UserId to posted data by Logged User in ASP.NET MVC 4

So what I'm doing might seem simple, but I don't know exactly how to do it.
I have already registered and logged in with an account (I'm using the default membership system used in ASP.NET MVC 4) and so I want to do add my UserId to some data I'm inserting to the database.
This is the model of the data I'm inserting:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Reroute.Models
{
public class Request
{
public int RequestId { get; set; }
// I want to add UserId based on my current session
public int UserId { get; set; }
public string OrderNumber { get; set; }
public string TrackingNumber { get; set; }
public string CurrentAddress { get; set; }
public string NewAddress { get; set; }
public string Comment { get; set; }
}
}
And the ActionResult (here's where I supposed I have to make the changes):
[HttpPost]
public ActionResult Create(Request collection)
{
try
{
_db.Requests.Add(collection);
_db.SaveChanges();
//return RedirectToAction("Index");
return Content("Done! Added to DB");
}
catch
{
return View();
}
}
Thanks
use this it gets u the userid ...
Membership.GetUser().ProviderUserKey
You can save the UserId of the authenticated user in Session after logging in:
Session["UserId"] = userId;
or since you are using FormsAuthentication you can either use the UserData property as shown here or do a nice-that-will-do-trick:
public SignInUser(string name, string id) {
// store the userid
FormsAuthentication.SetAuthCookie(name + '|' + id, false);
}
then retrieve the Name and UserId like this:
public int CurrentUserId
{
get
{
var context = HttpContext.Current;
if (context == null) return 0;
return context.Request.IsAuthenticated
? Convert.ToInt32(context.User.Identity.Name.Split('|')[1])
: 0;
}
}
public string CurrentUserName
{
get
{
var context = HttpContext.Current;
if (context == null) return string.Empty;
return context.Request.IsAuthenticated
? context.User.Identity.Name.Split('|')[0]
: string.Empty;
}
}
You can have those method and properties in a class so you have them in one place, I actually do it that way. Now, you can call it in your controller like so:
[HttpPost]
public ActionResult Create(Request collection)
{
try
{
collection.UserId = _authProvider.CurrentUserId;
// if you want to use session, I prefer the FormsAuthentication approach
// you need to do additional check that the Session has not expired (not null)
collection.UserId = Session["UserId"];
_db.Requests.Add(collection);
_db.SaveChanges();
//return RedirectToAction("Index");
return Content("Done! Added to DB");
}
catch
{
return View();
}
}
_authProvider is an instance of the class that has the code I gave above.
This should work.
var loggedInUserName=Thread.CurrentPrincipal.Identity.Name;
var user=Membership.GetUser(loggedInUserName);
var key = user.ProviderUserKey;
T
Assuming your Create also has a GET which is loaded up and used as the model for Create.cshtml, you would just need to set it explicitly in that ActionResult
public ActionResult Create()
{
Result model = new Result();
model.UserId = myUserId;
}
Then in your Create.cshtml you could have a hidden field for it:
#Html.HiddenFor(m => m.UserId)
I would still check in the POST to make sure the user doing the saving is allowed to be saving and hasn't spoofed your hidden field value to somebody completely different.

How do I pass data between controllers and overloaded actions?

I have one controller that takes a username and pass and checks against a database. IF the user is authenticated, I want to call an overloaded action on another controller.
My end goal is to authenticate a user against an old table from a MySQL db (I have this part working). Once the user is authenticated, I would like to be able to "automagically" forward the person to the built in MVC registration page but I would like to populate some fields in the view using data obtained from the first controller (the old databse info).
When I try something like what I have below I get an error about the Register() methods being ambiguous. I've also tried using the [ActionName("Register2")] attribute but then the error returned says it cant find a method named Register2.
public class MigrateAccountController : Controller
{
OldUserRepository oldDb = new OldUserRepository();
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(User u)
{
if (oldDb.isValid(u.username, u.password))
return RedirectToAction("Register", "Account", u);
return View(u);
}
}
public class AccountController : Controller
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult Register(User u)
{
return View(u);
}
public ActionResult Register()
{
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View();
}
}
First thing you cannot have the same action name on the same controller that is accessible on the same verb. You need to either change the action name or use a different HTTP verb:
public class AccountController : Controller
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
[HttpPost]
public ActionResult Register(User u)
{
return View(u);
}
public ActionResult Register()
{
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View();
}
}
and in order to pass data between actions, well, if you are using GET, you could pass them as query string parameters when redirecting.
Or IMHO a better way would be not to redirect in this case but simply return the corresponding view by passing it the proper view model:
[HttpPost]
public ActionResult Index(User u)
{
if (oldDb.isValid(u.username, u.password))
{
return View("~/Account/Register.aspx", u);
}
return View(u);
}
You can use the TempData values in this case.

Resources