Implement custom ModelBinder ASP.NET MVC - c#

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().

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.

Is there any way to have an endpoint using generics in a WebAPI with C#?

With .net core and newton JSON. Can I have an endpoint using generics? How should I request it to pass the generic type?
public async Task<JsonResult> SaveSetting<T>([FromBody] Filter<T> model)
{}
public class Filter<T>
{
public string GUID { get; set; }
public string Name { get; set; }
public FilterType FilterType { get; set; }
public T FilterRequestModel { get; set; }
}
It's unlikely that there's an easy way to make this work, if there's a way at all. More importantly, it would be highly inadvisable. Presumably you'd need to figure out what type T should be based on input from the UI, and then you'd be binding values to that type with JSON bindings. That means you'd be allowing the caller (who you cannot trust) to make your code instantiate and set properties on a C# type of their choosing, which represents a security vulnerability.
There's a good chance you can make this endpoint do what you want without generics: just use a JObject as the FilterRequestModel object. If you really do rely on actual C# types to accomplish what you want, you're still better off making your controller action use a JObject, and using some custom logic to translate it based on some user input, after checking that the type specified is something you want callers to be able to instantiate. Once you've created an object of a custom type, you can cast it to dynamic before passing it into a generic helper method, and the generic type will automatically be resolved at runtime to the type of the object you created.
public async Task<JsonResult> SaveSetting([FromBody] Filter<JObject> model)
{
Type filterRequestModelType = this.DetermineSafeModelType(model);
object typedFilterRequestModel = model.FilterRequestModel.ToObject(filterRequestModelType);
return SaveSettingHelper(model, (dynamic) typedFilterRequestModel);
}
public async Task<JsonResult> SaveSetting<T>([FromBody] Filter<JObject> model, T filterRequestModel) {...}
.NET CLI
dotnet new web --name "GenericApiExample"
cd GenericApiExample
dotnet add package SingleApi
Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// map generics endpoint
app.MapSingleApi("sapi",
// add your generic request handler
// for example, return the received data (already typed object)
x => Task.FromResult(x.Data),
// add assemblies for resolving received data types
typeof(List<>).Assembly, typeof(int).Assembly);
app.Run();
Example request, equivalent of Dictionary<string,int?[]>
POST /sapi/Dictionary(String-Array(Nullable(Int32)))
{"key1":[555,null,777]}
GitHub repository with examples

symfony's paramconverter equivalent in ASP.NET Web API

I'm pretty new in .Net community, please show mercy haha. I got two questions:
is there any similar implementation of symfony's #paramconverter annotation in ASP.NET (such as a converter attributes)?
e.g. the request url, no matter GET/POST/PUT/DELETE, the api request with an id of certain entity and we are able to convert/bind such id into a entity object(say productId in int and convert to Product object)
Expected PseudoCode:
[Route("products/{productId}/comments")]
[ProductConverter]
public HttpResponseMessage getProductComments(Product product) {
// product here not only with `productId` field, but already converted/bind into `Product`, through repository/data store
....
}
Is this good practise in .Net? I ask for this implementation because I think this pattern able to reduce duplicate code as API requests mainly rely on object id. I can even throw an exception if such Product object not found by such string.
It looks like ModelBinder is equiuvalent to symphony's parmconverter. You can read more about it here: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Here is sample:
First you have to implement IModelBinder. That's very basic implementation of it:
public class ProductBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(Product))
{
return false;
}
var id = (int)bindingContext.ValueProvider.GetValue("productId").ConvertTo(typeof(int));
// Create instance of your object
bindingContext.Model = new Product { Id = id };
return true;
}
}
Next you have to configure ASP.NET WebApi to use that binder. In WebApiConfig.cs file (or any other where you configure WebAPI) add the following line:
config.BindParameter(typeof(Product), new ProductBinder());
The final step is to create correct controller method. It's important to provide correct route parameter in order to correct binding.
[Route("products/{productId}/comments")]
public HttpResponseMessage getProductComments(Product product) {
}
I don't think that this is bad practice or good practices. As always it depends. For sure it reduce code duplication and introduce some order to your code. If the object with that id doesn't exists you can even try tweak this binder to return response 404 (not found)
Edit:
Using IModelBinder with dependency injection is a bit tricky but still possible. You have to write additional extension method:
public static void BindParameter(this HttpConfiguration config, Type type, Type binderType)
{
config.Services.Insert(typeof(ModelBinderProvider), 0, new SimpleModelBinderProvider(type, () => (IModelBinder)config.DependencyResolver.GetService(binderType)));
config.ParameterBindingRules.Insert(0, type, param => param.BindWithModelBinding());
}
It's based on orginal method found there. The only difference is that it expect type of the binder instead of instance. So you just call:
config.BindParameter(typeof(Product), typeof(ProductBinder));

Custom data annotation attribute not being validated

I am trying to make a custom validation using data annotations.
Trying to make the attribute, I have followed the question:
How to create Custom Data Annotation Validators
My attribute looks like this
internal class ExcludeDefaultAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
return false;
}
}
and the validation is called by:
internal static class TypeValidator
{
static public bool Validate(object item)
{
List<ValidationResult> results = new List<ValidationResult>();
ValidationContext context = new ValidationContext(item);
if (Validator.TryValidateObject(item, context, results))
{
return true;
}
else
{
string message = string.Format("Error validating item");
throw new TypeInvalidException(results, message);
}
}
}
So, here is the issue. My custom validation, currently, should always return false. So validation should always fail. However, whenever I try to validate an object that has this attribute on a field, it passes validation, which suggests that my custom validation attribute isn't being evaluated. I don't want to make any actual logic in the validation until I know it is actually running.
Am I missing something? All my research says I simply need to inherit from ValidationAttribute, but it isn't working.
According to the MSDN article, the TryValidateObject method will do the following:
This method evaluates each ValidationAttribute instance that is attached to the object type. It also checks whether each property that is marked with RequiredAttribute is provided. It does not recursively validate the property values of the object.
I tested this and it behaved as advertised using the syntax provided.
Edit
Per the comment below, using the following overload results in proper validation of all properties including those using custom attributes:
TryValidateObject(object instance, ValidationContext validationContext, ICollection<ValidationResult> validationResults, bool validateAllProperties)

Prevent MVC Action method from executing if a parameter is null

I've thought of a few ways of doing this but I want to get the community's view. I have a feeling that the answer is cringeworthily simple - I'm not afraid to look stupid (my kids took that fear away from me long ago!)
I'm writing an XML REST web service using MVC2. All XML Types that consumers of the web service will receive and send are governed by simple but extensive XSD, and these parameters will be bound from xml in the request body via a custom default model binder and value provider.
I have a goodly amount of controllers, each with a goodly amount of action methods (not excessive - just 'goodly' ;) ) - and in nearly every case these action methods are going to be accepting model types that are all reference types.
In practically every case it's going to be an error for the caller not to provide these parameter values, and as such a standard error message such as "The parameter {name} type:{ns:type} is required" can be sent back.
What I want to do is to be able to validate parameters are not null before an action method is executed; and then to return an ActionResult that represents the Error to the client (for this I already have an XMLResult type) without the action method itself having to validdate the parameters itself.
So, instead of:
public ActionResult ActionMethod(RefType model)
{
if(model == null)
return new Xml(new Error("'model' must be provided"));
}
Something like:
public ActionResult ActionMethod([NotNull]RefType model)
{
//model now guaranteed not to be null.
}
I know this is exactly the kind of cross-cutting that can be achieved in MVC.
It seems to me that either a base controller override of OnActionExecuting or a custom ActionFilter is the most likely way of doing this.
I'd also like to be able to extend the system so that it automatically picks up XML schema validation errors (added to ModelState during binding by a custom value provider) thus preventing the action method from continuing if any of the parameter values can't be loaded correctly because the XML request is badly formed.
Here's the implementation that I've come up with (while waiting for any better ideas :) )
It's a generic approach and I think is pretty scalable - allowing for hopefully a similar kind of depth to parameter validation as you get with model validation at the same time as providing the error auto-respond functionality (when model state contains one or more errors) that I was looking for.
I hope this isn't too much code for an SO answer(!); I had a load of documentation comments in there that I've taken out to keep it shorter.
So, in my scenario I have two types of model error that, if they occur, should block execution of the action method:
Failed schema validation of the XML from which a parameter value will be constructed
Missing (null) parameter value
Schema validation is currently performed during model binding, and automatically adds model errors to the ModelState - so that's great. So I need a way to perform the auto-null check.
In the end I created two classes to wrap up the validation:
[AttributeUsage(AttributeTargets.Parameter,
AllowMultiple = false, Inherited = false)]
public abstract class ValidateParameterAttribute : Attribute
{
private bool _continueValidation = false;
public bool ContinueValidation
{ get { return _continueValidation; } set { _continueValidation = value; } }
private int _order = -1;
public int Order { get { return _order; } set { _order = value; } }
public abstract bool Validate
(ControllerContext context, ParameterDescriptor parameter, object value);
public abstract ModelError CreateModelError
(ControllerContext context, ParameterDescriptor parameter, object value);
public virtual ModelError GetModelError
(ControllerContext context, ParameterDescriptor parameter, object value)
{
if (!Validate(context, parameter, value))
return CreateModelError(context, parameter, value);
return null;
}
}
[AttributeUsage(AttributeTargets.Parameter,
AllowMultiple = false, Inherited = false)]
public class RequiredParameterAttribute : ValidateParameterAttribute
{
private object _missing = null;
public object MissingValue
{ get { return _missing; } set { _missing = value; } }
public virtual object GetMissingValue
(ControllerContext context, ParameterDescriptor parameter)
{
//using a virtual method so that a missing value could be selected based
//on the current controller's state.
return MissingValue;
}
public override bool Validate
(ControllerContext context, ParameterDescriptor parameter, object value)
{
return !object.Equals(value, GetMissingValue(context, parameter));
}
public override ModelError CreateModelError
(ControllerContext context, ParameterDescriptor parameter, object value)
{
return new ModelError(
string.Format("Parameter {0} is required", parameter.ParameterName));
}
}
With this I can then do this:
public void ActionMethod([RequiredParameter]MyModel p1){ /* code here */ }
But this on its own doesn't do anything of course, so now we need something to actually trigger the validation, to get the model errors and add them to model state.
Enter the ParameterValidationAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = false)]
public class ParameterValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var paramDescriptors = filterContext.ActionDescriptor.GetParameters();
if (paramDescriptors == null || paramDescriptors.Length == 0)
return;
var parameters = filterContext.ActionParameters;
object paramvalue = null;
ModelStateDictionary modelState
= filterContext.Controller.ViewData.ModelState;
ModelState paramState = null;
ModelError modelError = null;
foreach (var paramDescriptor in paramDescriptors)
{
paramState = modelState[paramDescriptor.ParameterName];
//fetch the parameter value, if this fails we simply end up with null
parameters.TryGetValue(paramDescriptor.ParameterName, out paramvalue);
foreach (var validator in paramDescriptor.GetCustomAttributes
(typeof(ValidateParameterAttribute), false)
.Cast<ValidateParameterAttribute>().OrderBy(a => a.Order)
)
{
modelError =
validator.GetModelError(filterContext, paramDescriptor, paramvalue);
if(modelError!=null)
{
//create model state for this parameter if not already present
if (paramState == null)
modelState[paramDescriptor.ParameterName] =
paramState = new ModelState();
paramState.Errors.Add(modelError);
//break if no more validation should be performed
if (validator.ContinueValidation == false)
break;
}
}
}
base.OnActionExecuting(filterContext);
}
}
Whew! Nearly there now...
So, now we can do this:
[ParameterValidation]
public ActionResult([RequiredParameter]MyModel p1)
{
//ViewData.ModelState["p1"] will now contain an error if null when called
}
To complete the puzzle we need something that can investigate the model errors and automatically respond if there are any. This is the least tidy of the classes (I hate the name and the parameter type used) and I'll probably change it in my project, but it works so I'll post it anyway:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = false)]
public abstract class RespondWithModelErrorsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ModelStateDictionary modelState =
filterContext.Controller.ViewData.ModelState;
if (modelState.Any(kvp => kvp.Value.Errors.Count > 0))
filterContext.Result = CreateResult(filterContext,
modelState.Where(kvp => kvp.Value.Errors.Count > 0));
base.OnActionExecuting(filterContext);
}
public abstract ActionResult CreateResult(
ActionExecutingContext filterContext,
IEnumerable<KeyValuePair<string, ModelState>> modelStateWithErrors);
}
In my application I have an XmlResult that takes a Model instance and serializes to the response using either DataContractSerializer or XmlSerializer - so I've then created RespondWithXmlModelErrorsAttribute that inherits from this last type to formulate one of those with the model as an Errors class that simply contains each of the model errors as strings. The Response Code is also automatically set to 400 Bad Request.
Thus, now I can do this:
[ParameterValidation]
[RespondWithXmlModelErrors(Order = int.MaxValue)]
public ActionResult([RequiredParameter]MyModel p1)
{
//now if p1 is null, the method won't even be called.
}
In the case of web pages this last stage won't necessarily be required, since model errors are typically included in a re-rendering of page that sent the data in the first place, and the existing MVC approach suits this fine.
But for web services (either XML or JSON) being able to offload error reporting to something else makes writing the actual action method a lot easier - and much more expressive, I feel.
Well you could add constraints using regular expressions to individual route values. Then, if these constraints are not upheld, the action method will not be hit:
routes.MapRoute ("SomeWebService", "service/{userId}",
new { controller = "Service", action = "UserService" },
new { userId = #"\d+" });
Alternatively you could create custom constraints to validate route values together as a pack. This would probably be a better strategy for you. Have a look here: Creating a Custom Route Constraint

Categories

Resources