asp.net mvc modelbinding for username and date - c#

I have a logging controller in my project that gets Log data from javascript client side and sends to server.
Log Model ils like this.
public class Log
{
public string Event { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
public string Username { get; set; }
}
My controller action is like this.
[HttpPost]
public JsonResult Save(Log log)
{
log.Username = User.Identity.Name;
log.Date = DateTime.now;
return Json(new { message = "ok" }, JsonRequestBehavior.AllowGet);
}
when users sended the log data, I need to set the username and date. Is there another way with model binding or else? Automatically set the context.username and date time of log.

IModelBinder is the preferred way to go. You implement the interface and register it in Global.asax file
ModelBinders.Binders.Add(typeof(Log), new LogModelBinder());
What I don't like about this approach is that you have to be aware that there is a custom model binder for this type implemented somewhere and it can cause confusion if you forget about it, or you have a new developer working on the project and model starts behaving strange. It is just not obvious enough what is changing the object.
I like to have all my custom logic implemented in the controller itself. You can override OnActionExecuting and inject the properties there, better yet, have a BaseController that all your controllers inherit from and do your custom logic in there.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
object model = filterContext.ActionParameters
.TryGetValue("model", out model)
? model : null;
if (model != null)
{
var logModel = model as Log;
if (logModel != null)
{
logModel.Date = DateTime.Now;
logModel.Username = filterContext.HttpContext.User.Identity.Name;
}
}
}

Related

Custom validation at controller level in web api

i'm stuck with a problem in a net standard 2.0 library and i really don't know how to approach it.
let's say i have a controller like this:
[Route("{action}/{Id}")]
[HttpPut]
public void UpdateUser([FromBody] UserDTO user, long id )
{
_service.UpdateUser(user, id);
}
easy, it works.
now i want to add some custom validation.
i know i can use System.ComponentModel.DataAnnotations on the DTO, but the message when there's a bad request is... bad.
i tried to use ValidationAttribute with a isValid,a FormatErrorMessage and add it to my controller like so
[MyValidation]
[Route("{action}/{Id}")]
[HttpPut]
public void UpdateUser([FromBody] UserDTO user, long id )
{
_service.UpdateUser(user, id);
}
but it never reached the MyValidation code, it just skipped it.
is there a way i can do this so that i can do all the validation i want and check it against my db and do all the stuff i need?
how can i proceed?
all the guides i found are related to .core or .mvc, things i can't use cos i'm on a net standard library
even a guide would be wonderful!
thanks to anyone who wants to help :D
My Solution!
so, after lots of research i found out that... it's not possibile to do it.
at least from what i understood/tried
i did a bit of a workaround because what i used can't be used on a net standard library, whatever, it seems net standard doesn't have this kind of stuff to work with.
i used the IActionFilter.
i created a class and inherited the filter and put in the two methods that it requires, OnActionExecuted and OnActionExecuting.
public class UserFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
}
to read what it's inside the payload you can use
UserDTO model = new UserDTO();
model = context.ActionArguments["user"] as UserDTO;
where user is the "name" of your payload.
to know what it is, you can use the debugger and read what's inside context.actionArguments.
remember to use the " " between that name.
you have to cast it using the "as" keyword to your model.
now you have access to your payload and you can do your own custom validator!!!
in my case i created a list of string that will contain the string errors, validate id and throw an error if the list has at least one element.
example :
UserDTO model = new UserDTO();
List<string> CustomError = new List<string>();
model = context.ActionArguments["user"] as UserDTO;
if (model.Age == 0) //dumb control, just for the sake of the example
{
CustomError.Add("age cannot be 0!")
}
//other controls
if (CustomError.Count > 0)
{
context.Result = new BadRequestObjectResult(CustomError);
}
the last if is important! if the list you made has at least one element, it will throw a bad request (error 400) and you will get back a json with all the errors.
and on top of the controller you want to custom validate use
[HttpPost]
[TypeFilter(typeof(UserFilter))]
public IValidation<> PostUser(UserDTO user)
{
//your controller logic
}
hope it will help somebody in the future who got stucked like me, trying to custom validate data in a net standard, which it seems is impossible
You need to implement the ValidationAttribute class and then you can write your own logic and use as data annotation on your models.
Sample Implementation
public class ValidJoinDate : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
DateTime _dateJoin = Convert.ToDateTime(value);
if (_dateJoin < DateTime.Now)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult
("Join date can not be greater than current date.");
}
}
}
And use in this way in your model properties:
public class Customer
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}"
, ApplyFormatInEditMode = true)]
[ValidJoinDate(ErrorMessage=
"Join Date can not be greater than current date")]
public DateTime JoinDate { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString =
"{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[ValidLastDeliveryDate(ErrorMessage =
"Last Delivery Date can not be less than Join Date")]
public DateTime LastDeliveryDate { get; set; }
}
Hope this solved your problem 👍

aspnetboilerplate custom validation throws exception

I'm using aspnetboilerplate framework in a .Net Core project. I have a custom validation for my view model following on official documentation.
First I have a simple view model with custom validation:
public class EditPropertyViewModel : ICustomValidate
{
public long Id { get; set; }
public long? ParentId { get; set; }
public string Title { get; set; }
public void AddValidationErrors(CustomValidationContext context)
{
if (Id == ParentId)
context.Results.Add(new ValidationResult("Property cannot be parent of itself!", new [] { "ParentId" } ));
}
}
Then my controller is like this:
[HttpPost]
public async Task<IActionResult> Edit(EditPropertyViewModel model)
{
if (ModelState.IsValid)
{
/* Update property here and return */
}
return View(model);
}
But when I run the project, this exception occures:
AbpValidationException: Method arguments are not valid! See ValidationErrors for details.
That means my custom validation has been executed before ModelState.IsValid and there is no chance to handle that exception and show a user friendly message to the user. Disabling validation by [DisableValidation] skips this exception but my validation logic is skipped too. I also tryed to use .NET's standard IValidatableObject interface instead of the abp's ICustomValidate but this not helped me to solve the problem.

Is it possible to use Web API model validation on query parameters?

I am currently trying to write a Web API application where one of the parameters I'd like to validate is a query parameter (that is, I wish to pass it in in the form /route?offset=0&limit=100):
[HttpGet]
public async Task<HttpResponseMessage> GetItems(
int offset = 0,
int limit = 100)
{
if (!ModelState.IsValid)
{
// Handle error
}
// Handle request
}
In particular, I want to ensure that "offset" is greater than 0, since a negative number will cause the database to throw an exception.
I went straight for the logical approach of attaching a ValidationAttribute to it:
[HttpGet]
public async Task<HttpResponseMessage> GetItems(
[Range(0, int.MaxValue)] int offset = 0,
int limit = 100)
{
if (!ModelState.IsValid)
{
// Handle error
}
// Handle request
}
This does not cause any errors at all.
After a lot of painful debugging into ASP.NET, it appears to me that this may be simply impossible. In particular, because the offset parameter is a method parameter rather than a field, the ModelMetadata is created using GetMetadataForType rather than GetMetadataForProperty, which means that the PropertyName will be null. In turn, this means that AssociatedValidatorProvider calls GetValidatorsForType, which uses an empty list of attributes even though the parameter had attributes on it.
I don't even see a way to write a custom ModelValidatorProvider in such a way as to get at that information, because the information that this was a function parameter seems to have been lost long ago. One way to do that might be to derive from the ModelMetadata class and use a custom ModelMetadataProvider as well but there's basically no documentation for any of this code so it would be a crapshoot that it actually works correctly, and I'd have to duplicate all of the DataAnnotationsModelValidatorProvider logic.
Can someone prove me wrong? Can someone show me how to get validation to work on a parameter, similar to how the BindAttribute works in MVC? Or is there an alternative way to bind query parameters that will allow the validation to work correctly?
You can create a view request model class with those 2 properties and apply your validation attributes on the properties.
public class Req
{
[Range(1, Int32.MaxValue, ErrorMessage = "Enter number greater than 1 ")]
public int Offset { set; get; }
public int Limit { set; get; }
}
And in your method, use this as the parameter
public HttpResponseMessage Post(Req model)
{
if (!ModelState.IsValid)
{
// to do :return something. May be the validation errors?
var errors = new List<string>();
foreach (var modelStateVal in ModelState.Values.Select(d => d.Errors))
{
errors.AddRange(modelStateVal.Select(error => error.ErrorMessage));
}
return Request.CreateResponse(HttpStatusCode.OK, new { Status = "Error",
Errors = errors });
}
// Model validation passed. Use model.Offset and Model.Limit as needed
return Request.CreateResponse(HttpStatusCode.OK);
}
When a request comes, the default model binder will map the request params(limit and offset, assuming they are part of the request) to an object of Req class and you will be able to call ModelState.IsValid method.
For .Net 5.0 and validating query parameters:
using System.ComponentModel.DataAnnotations;
namespace XXApi.Models
{
public class LoginModel
{
[Required]
public string username { get; set; }
public string password { get; set; }
}
}
namespace XXApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
[HttpGet]
public ActionResult login([FromQuery] LoginModel model)
{
//.Net automatically validates model from the URL string
//and gets here after validation succeeded
}
}
}
if (Offset < 1)
ModelState.AddModelError(string.Empty, "Enter number greater than 1");
if (ModelState.IsValid)
{
}

Best practice for validating complex cases in ASP.NET/MVC?

We're always told that a Controller should be skinny and that validation should be done in the Model, not the Controller. But consider the following example.
Here's is a simple Model and Controller for handling the POST from an edit screen, on which we can edit a Person object.
public class PersonEditModel
{
[Required(ErrorMessage = "No ID Passed")]
public int ID { get; set; }
[Required(ErrorMessage = "First name Required")]
[StringLength(50,ErrorMessage = "Must be under 50 characters")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name Required")]
[StringLength(50,ErrorMessage = "Must be under 50 characters")]
public string LastName { get; set; }
}
public class PersonController : Controller
{
// [HttpGet]View, [HttpGet]Edit Controller methods omitted for brevity
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
// save changes to the record
return RedirectToAction("View", "Person", new { ID = model.ID});
}
}
The Model performs two kinds of validation here. It validates FirstName and LastName, but it also validates the private key (ID) used to access the record we wish to change. Should this validation be done in the Model also?
What if we then want to expand validation (as we should) to include a check to see if this record exists?
Normally, I would validate this in the controller:
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
using(DatabaseContext db = new DatabaseContext())
{
var _person = db.Persons.Where(x => x.ID == model.ID);
if(_person == null)
{
ModelState.AddError("This person does not exist!");
// not sure how we got here, malicious post maybe. Who knows.
// so since the ID is invalid, we return the user to the Person List
return RedirectToAction("List", Person");
}
// save changes
}
// if we got here, everything likely worked out fine
return RedirectToAction("View", "Person", new { ID = model.ID});
}
Is this bad practice? Should I be checking if the record exists in some kind of complex custom validation method in the model? Should I put it somewhere else entirely?
UPDATE
On a related note. Should a ViewModel contain the methods to populate the data?
Which of these is better practice - this
public class PersonViewModel
{
public Person person { get; set; }
public PersonViewModel(int ID){
using(DatabaseContext db = new DatabaseContext())
{
this.person = db.Persons.Where(x => x.ID == ID);
}
}
}
[HttpPost]
public ActionResult View(int ID)
{
return View("View", new PersonViewModel(ID));
}
Or this?
public class PersonViewModel
{
public Person person { get; set; }
}
[HttpPost]
public ActionResult View(int ID)
{
PersonViewModel model = new PersonViewModel();
using(DatabaseContext db = new DatabaseContext())
{
model.person = db.Persons.Where(x => x.ID == ID);
}
return View("View", model);
}
I generally prefer FluentValidation for all purposes. It also has a Nuget to install it out-of the box in VS.
Sample Validation Code from here:
using FluentValidation;
public class CustomerValidator: AbstractValidator<Customer> {
public CustomerValidator() {
RuleFor(customer => customer.Surname).NotEmpty();
RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
RuleFor(customer => customer.Address).Length(20, 250);
RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
}
private bool BeAValidPostcode(string postcode) {
// custom postcode validating logic goes here
}
}
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);
bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors;
See?? It is very easy to validate any kind of models with Fluent Validation with clean methods. You can consider going through FluentValidation Documentation.
Where to validate?
Suppose you have a model as below:
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
virtual public ICollection<Image> Images { get; set; }
}
then, you will define another validator model in a similar class library or preferably a new class library that handles validation for all the models in the project.
public class CategoryValidator : AbstractValidator<Category>
{
public CategoryValidator()
{
RuleFor(x => x.Name).NotEmpty().WithMessage("Category name is required.");
}
}
So, you can do it in a separate validator model keeping your methods and domain models as clean as possible.
When we talk about Model, it includes your DAL and your business layer. For small apps or demos it is not unusual to see that kind of code in a controller, but normally you should give that role to the business or data layer :
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
// Validation round one, using attributes defined on your properties
// The model binder checks for you if required fields are submitted, with correct length
if(ModelState.IsValid)
{
// Validation round two, we push our model to the business layer
var errorMessage = this.personService.Update(model);
// some error has returned from the business layer
if(!string.IsNullOrEmpty(errorMessage))
{
// Error is added to be displayed to the user
ModelState.AddModelError(errorMessage);
}
else
{
// Update successfull
return RedirectToAction("View", "Person", new { ID = model.ID});
}
}
// Back to our form with current model values, as they're still in the ModelState
return View();
}
Here the goal is to free the controller from business logic validation and usage of the data context. It pushes submitted data and is notified if errors occurred. I used a string variable, but you can implement error management as you like. Evolving your business rules won't impact your controller at all.
There's absolutely nothing wrong with this. Your controllers are responsible for directing the flow of control when it comes to which view to be shown to your users. Part of doing that is ensuring that a view is getting a model that is in a usable state.
The controller doesn't care what the model is, or what the model contains, but it does care that it's valid. It's why ModelState.IsValid is so important, because the controller doesn't have to know how that validation is performed or what directly makes a model valid. Typically, any validation that needs to take place after ModelState.IsValid, can be pushed to another layer of your application, which again enforces separation-of-concerns.

Automatically binding a model property if it is present

I'm not sure i know the exact terminology for what i want to do, so i'll just describe what i want, and hopefully someone will give me the answer or direct me to the right place...
I want some (maybe all) of the models on my site to inherit from IGeneralSettings -
public interface IGeneralSettings
{
User CurrentUser { get; set; }
UserSettings PersonalSettings { get; set; }
}
and if the current user is authenticated/logged in, I will check if the current controller's model is of a type that implements IGeneralSettings, and if it is, I will fill the data with the current user data.
I am using Windsor Castle as an IoC container.
I don't want to use the property bag for this.
I don't want to call a method that will do this in each action in each controller, I want this to be injected automatically each request - meaning this code will be written once.
Is this possible ?
I would recommend adding your CurrentUser and PersonalSettings objects to a base controller and populating it each request (if they are authenticated). Then you can access them in a custom model binder because you have access to the controller context.
Example of one way to do it:
Base controller that your controllers inherit
public class BaseController : Controller
{
public BaseController()
{
//You can load the CurrentUser and Settings here
CurrentUser = new User
{
Id = 1
};
PersonalSettings = new UserSettings
{
Id = 1
};
}
public User CurrentUser { get; set; }
public UserSettings PersonalSettings { get; set; }
}
ModelBinder
public class ThingModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var model = (Thing)base.BindModel(controllerContext, bindingContext);
var baseController = controllerContext.Controller as BaseController;
if (baseController != null)
{
model.CurrentUser = baseController.CurrentUser;
model.PersonalSettings = baseController.PersonalSettings;
}
return model;
}
}
Add ModelBinder in Application_Start of Global.asax
ModelBinders.Binders.Add(typeof(Thing), new ThingModelBinder());
Controller
public class HomeController : BaseController
{
[HttpPost]
public ActionResult Save(Thing model)
{
//model has CurrentUser and PersonalSettings set
//without any values being posted.
//You can also access the values on the BaseController
//if you don't want to automatically bind it.
return View();
}
}
Another way I found in my last project:
Define a extension method for Controller, like:
public static Models.User GetUser(this Controller controller)
{
string name = controller.User.Identity.Name;
string alias = name.Substring(name.LastIndexOf('\\') + 1);
string domain = name.Substring(0, name.IndexOf('\\') - 1);
User user = User.ByAlias(alias);
if (user == null)
{
user = new User();
user.Name = controller.User.Identity.Name;
user.Alias = alias;
user.Domain = domain;
user.Description = "Automatically registered";
Guid userId = Models.User.Create(user, Guid.Empty);
user = User.ByAlias(alias);
}
return user;
}
That would not break your current class structure.

Categories

Resources