Creating custom model validation attribute - c#

I'm trying to emulate the behavior of the [ApiController] attribute for model validation, however I'd like to return a JSON object I've made with the validation errors in an Error array within the JSON.
The challenge I'm facing is that I'm unsure how to access validation errors from within the Attribute and I'd like to use the attribute at the class level, so it will run on all controller methods without need of supplying the attribute for each action.
Any direction would be much appreciated.
edit: linked duplicate is how to create custom attribute. I'm looking how to access model validation errors from within an attribute.

I was able to figure out my issue. I was able to utilize ModelState.IsValid in an OnActionExecuting method to access the errors. Unfortunately I'm not familiar enough with making a class level attribute so I have to apply this to all post/patch methods in order for it to work. If someone else comes up with a way to do that easily, let me know!
Project.Structure is for formatting JSON for those curious.
using System;
using System.Collections.Generic;
using System.Linq;
using Project.Structure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Project.Attributes
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errorList = new List<string>();
foreach (var modelError in context.ModelState.Values)
{
errorList.AddRange(modelError.Errors.Select(error => error.ErrorMessage));
}
var response = new ResponseDto<object>
{
Success = false,
TransactionId = Guid.NewGuid().ToString(),
ResponseType = ResponseType.Operation.Description(),
Response = null,
Errors = errorList,
Warnings = null
};
context.Result = new BadRequestObjectResult(response);
}
}
}
}

Related

Can C# use attribute to get return data from methods inside controllers and services?

Currently using asp core to build a web service system
I hope to obtain the return data of the service method in a specific controller through attribute
The following are examples
[HttpPost, Route("list")]
[CustomAttribute]
public IActionResult GetList([FromBody] NewsClassDto request)
{
var data = newsClassService.GetList(model);
return OkResponse(data);
}
NewsClassService Examples
public NewsClassDto GetList(NewsClassDto dto)
{
var daoClassData = _newsClassDao.GetList(dto);
var daoData = _newsDataDao.GetList(dto);
/** logical processing **/
return daoClassData;
}
I want to record through
[CustomAttribute]
newsClassService.GetList(model);
data returns content and
_newsClassDao.GetList(dto);
_newsDataDao.GetList(dto);
daoClassData returns content and daoData returns content , but I don't know how to implement this in Attribute
Yes, this is very common. What you need to do is create an action filter attribute. You can read about filters in general here.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace aspnet6test;
public class CustomFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext context)
{
if (context.Result is OkObjectResult okResult)
{
// Here, okResult.Value will contain the result data. You'll have to
// explore with a series of IF statements whether or not is the data you're
// looking for. Example:
if (okResult.Value is List<SomeDto> listOfDtos)
{
// So it is a list of wanted SomeDto objects. Do your magic.
}
}
}
}
The above example runs the code after the result has been returned by the controller action, which I believe satisfies the requirements in your question.
It would be used like this:
[HttpPost("list")] // <-- The HTTP verp attributes are also Route attributes
[CustomFilter]
public IActionResult GetList([FromBody] NewsClassDto request)
{
var data = newsClassService.GetList(model);
return Ok(data); // <-- I believe this is the correct way.
}
The filter may accept properties and even constructor values, but that's another topic.

Validation Filter in Azure Function

Problem
I am using fluent validation for model validation and I want it to be done by ValidationFilter in azure function. I already did it in asp.net core app but I don't know how to do it in azure functions
Code
MyValidator
using FluentValidation;
namespace MyLocker.Models.Validations
{
public class PortfolioFolderVMValidator : AbstractValidator<PortfolioFolderVM>
{
public PortfolioFolderVMValidator()
{
RuleFor(reg => reg.Title).NotEmpty().WithName("Title").WithMessage("{PropertyName} is required");
RuleFor(reg => reg.UserId).NotEmpty().WithName("UserId").WithMessage("{PropertyName} is required");
}
}
}
Validate Model Action Filter
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Mvc.Filters;
using MyLocker.Models.Common;
namespace MyLocker.WebAPI.Attributes
{
public sealed class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Keys.SelectMany(key => context.ModelState[key].Errors.Select(error => new ValidationError(key, error.ErrorMessage))).ToList();
context.Result = new ContentActionResult<IList<ValidationError>>(HttpStatusCode.BadRequest, errors, "BAD REQUEST", null);
}
}
}
}
Startup
after that add it in startup.cs class like this
services.AddMvc(opt => opt.Filters.Add(typeof(ValidateModelAttribute)))
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PortfolioFileModelValidator>())
I tried it in azure function but there are different classes and interface in it.
At present , there are 2 types of filter are available with Azure function:
FunctionInvocationFilterAttribute
As the name indicates, Filter used to execute PRE and POST processing logic when target job function is invoked.
FunctionExceptionFilterAttribute
Exception Filter will be called whenever there is an exception thrown by the Azure Function.
But you can write a wrapper on top of Azure function which will help you write the clean code.
For the wrapper based implementation , you can refer below code repo:
https://gist.github.com/bruceharrison1984/e5a6aa726ce726b15958f29267bd9385#file-fluentvalidation-validator-cs
Also alternatively you can implement the pipeline for validation the data model passed to azure function. You can find the complete reference in below repo:
https://github.com/kevbite/AzureFunctions.GreenPipes/tree/master/src/AzureFunctions.GreenPipes
Out of these 2 approach, Wrappers is more cleaner and easier to implement.
Let me know if you need any other help.

Get JSON data out of Umbraco

I am a frontend developer so forgive my lack of ability to explain my issue.
I am trying to create some pages in an Umbraco project that display data using Vue.js. For this, I am trying to set up a custom API controller that will return the data I want, when called.
A simple example would be that I want to return all blog articles. Below is the code I have currently got:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Web;
using System.Web.Http;
using Umbraco.Web.WebApi;
using Umbraco.Web.PublishedContentModels;
using Newtonsoft.Json;
namespace Controllers.WebAPI.Qwerty
{
[Route("api/[controller]")]
public class PostsApiController : UmbracoApiController
{
[HttpGet]
public string Test()
{
return "qwerty";
}
}
}
I've read numerous articles and just can't seem to grasp what I need to do to query Umbraco for the data I want back?
I've tried adding
var content = Umbraco.TypedContent(1122);
And then returning that but I get errors stating:
(local variable) Umbraco.Core.Models.IPublishedContent content
Cannot implicitly convert type 'Umbraco.Core.Models.IPublishedContent' to 'string'
I have then tried serialising the var content but I get stuck with:
Self referencing loop detected for property 'FooterCtalink' with type
'Umbraco.Web.PublishedContentModels.Blog'. Path
'ContentSet[0].FeaturedProducts[0].Features[0].ContentSet[0]'.
Any help would be fantastic!
EDIT:
I have no edited the controller to be like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Web;
using Umbraco.Web.WebApi;
using Umbraco.Web.PublishedContentModels;
using Newtonsoft.Json;
using System.Web.Mvc;
using DTOs.PostDTO;
namespace Controllers.WebAPI.Qwerty
{
[Route("api/[controller]")]
public class PostsApiController : UmbracoApiController
{
[HttpGet]
public PostDTO Test()
{
// 1. Get content from umbraco
var content = Umbraco.TypedContent(1122);
// 2. Create instance of your own DTO
var myDTO = new PostDTO();
// 3. Pupulate your DTO
myDTO.Url = content.Url;
// 4. return it
return myDTO;
}
}
}
And created a DTO like so:
namespace DTOs.PostDTO
{
public class PostDTO
{
public string Url { get; set; }
}
}
However, when console logging my data after the ajax request, I only only getting 1122.
The issue is that you can't return a .NET Object in JSON that has the circular dependency.
To solve your problem, you can simply follow the below steps:
Create your own DTO & add required properties in that.
Fetch content from Umbraco API in C# & populate your custom DTO object.
Return that DTO from JsonResult.
Your code will look like below:
[Route("api/[controller]")]
public class PostsApiController : UmbracoApiController
{
[HttpGet]
public MyDTO Test()
{
// 1. Get content from umbraco
var content = Umbraco.TypedContent(1122);
// 2. Create instance of your own DTO
var myDTO = new MyDTO();
// 3. Pupulate your DTO
myDTO.SomeProperty = content.SomeProperty;
// 4. return it
return myDTO;
}
}
You are on the right track.
I think you need to return ActionResult instead of string.
Something like:
[HttpGet]
public ActionResult Test()
{
var content = Umbraco.TypedContent(1122);
return new JsonResult(content);
}
This should return the umbraco object as Json.

WebAPI POST [FromBody] Not Binding and Not Throwing Up Error

I'm trying to understand why my JSON Object when casted to an specficied object is giving me NULL when one of the attributes sent im my json, which is a number, is larger than Int32. My whole object is not binding and not not throwing up any error, just becoming null.
Now my situation. I started a new project just to understand it, and I all the question over here I could found.
I do have this model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TestJson.Models
{
public class Credential
{
public int IdSource { get; set; }
public DateTime TransactionDate { get; set; }
}
}
I have this Controller:
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using TestJson.Models;
namespace TestJson.Controllers
{
[RoutePrefix("PreAuth")]
public class PreAuthController : ApiController
{
[HttpPost]
[ActionName("PostObject")]
public async Task<IHttpActionResult> PostObject([FromBody]Credential input)
{
return await Task.FromResult(Ok(input));
}
[HttpPost]
[ActionName("PostJObject")]
public async Task<IHttpActionResult> PostJObject([FromBody]JObject input)
{
Credential Credential = input.ToObject<Credential>();
return await Task.FromResult(Ok(Credential));
}
}
}
And I have this JSON object:
{
"IdSource": 11111111111111,
"TransactionDate": "2017-11-24T11:59:01.7567643-02:00"
}
So, when I calling api/PreAuth/PostObject within the above JSON, I just get null as parameter casted to my object.
But if I call the same JSON /api/PreAuth/PostJObject, I can receive the given error below:
{
"Message": "An error has occurred.",
"ExceptionMessage": "Value was either too large or too small for an Int32.",
"ExceptionType": "System.OverflowException",
"StackTrace": " em System.Convert.ToInt32(Int64 value)..."
}
So, If I change my JSON object modifying my IdSource attribute to a shorter number, both of my methods works fine because it can fit to int limits.
My point is; Why when I call the method PostObject expecting a specified object in parameter and it couldn't cast my JSON into it properly it is giving me null and not filling my others properties?
The best scenario would be at least see some error, but I just get nothing at all. All my object is swallowed and it becomes like an stealth error and the caller don't know exactly which attribute he is giving wrong.
How can I workaround this using object as parameter instead JObject?
You should configure your JsonFormatter on your HttpConfiguration (application Startup).
An example:
protected virtual void ConfigureFormatter(HttpConfiguration config)
{
JsonConvert.DefaultSettings = () =>
{
JsonSerializerSettings result = new JsonSerializerSettings();
//Include the type name to be able to deserialize into the derived instead of the base type.
result.TypeNameHandling = TypeNameHandling.Auto;
//Do not include null properties in serialized JSON.
result.NullValueHandling = NullValueHandling.Ignore;
return result;
};
JsonMediaTypeFormatter jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings = JsonConvert.DefaultSettings();
config.Formatters.Clear();
config.Formatters.Add(jsonFormatter);
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
}
EDIT
The previous answer works for extended objects (I thought your were having null values on that).
You can however add a custom filter for filtering valid models like explained here:
Capture exception during request deserialization in WebAPI C#

Implement custom ModelBinder ASP.NET MVC

I am having the same problem many people already had - Model Binder doesn't accept localized decimal input. In all of the threads, here and other forums, the recommended solution is implementing a custom ModelBinder.
My problem is that those solutions somehow don't work for me. Let's use this solution for example: comma decimal seperator in asp.net mvc 5
When I reference all namespaces, two errors remain:
Error CS0115 'DecimalModelBinder.BindModel(ControllerContext,
ModelBindingContext)': no suitable method found to override ...
and
Error CS0173 Type of conditional expression cannot be determined
because there is no implicit conversion between 'bool' and 'decimal'
Where the second one references the entire return statement.
Did something change in the MVC Framework, so this code is outdated, or am I doing something wrong?
The code I ended up with is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.ModelBinding;
using System.Web.Mvc;
namespace AetMuzickaOprema.App_Start
{
public class DecimalModelBinder : System.Web.ModelBinding.DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, System.Web.ModelBinding.ModelBindingContext bindingContext) //first error
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return valueProviderResult == null ? base.BindModel(controllerContext, bindingContext) : Convert.ToDecimal(valueProviderResult.AttemptedValue); //second error
}
}
}
Model property in question:
[Required]
[Range(typeof(decimal), "0", "999999999")]
public decimal Price { get; set; }
It seems ultimately what you are trying to achieve is property-level binding such as this?:
[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}
If that is correct, this post offers a solution: Custom model binder for a property
Thanks.
In ASP.NET Core 1.1 (Microsoft.AspNetCore.Mvc.Core.Abstraction, 1.0.1.0) you'll see the following interface
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
//
// Summary:
// Defines an interface for model binders.
public interface IModelBinder
{
//
// Summary:
// Attempts to bind a model.
//
// Parameters:
// bindingContext:
// The Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext.
//
// Returns:
// A System.Threading.Tasks.Task which will complete when the model binding process
// completes.
// If model binding was successful, the Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext.Result
// should have Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult.IsModelSet
// set to true.
// A model binder that completes successfully should set Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext.Result
// to a value returned from Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult.Success(System.Object).
Task BindModelAsync(ModelBindingContext bindingContext);
}
}
Model binders are defined in Microsoft.AspNetCore.Mvc.ModelBinding.Binders namespace, assembly Microsoft.AspNetCore.Mvc.Core, Version=1.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60. None of them seem to expose BindModel() method, even less a virtual one. It looks like you are trying to override a non-existing method.
A better approach is to take an existing ModelBinder, one that best suits your needs, inherit from it, and override ModelBindAsync().

Categories

Resources