I'm working on a .NET Core 2 API project and have been trying to implement a universal string trim model binder that would trim all string values of provided request parameters and field values. So far I have had mixed results and am struggling to find working example that would point me in the right direction. I've been trying to implement the same model binder as posted by Vikash Kumar.
This model binder works fine for all string values that are passed into controller actions via direct parameters, such as public IActionResult Profile(string username), but for string fields in complex objects the BindModelAsync method of the TrimmingModelBinder class never gets called. An example of an HttpPost action in my controller would be public IActionResult Profile([FormBody] ProfileLookupModel model). The model binder does not seem to check the fields of the complex model. It also doesn't work for fields that are Lists of strings.
I recall prior to .NET Core, specifying a string trim model binder would recursively check each field of complex models, even models within complex models. This doesn't seem to be the case in .NET Core, but I might be wrong. My project is targeting the netcoreapp2.0 framework.
I'm curious if anyone has had the same issue as me and possibly found a solution for it.
Note: I haven't posted any sample code as it is the same as the code from the referenced article.
I'll add my 2 cents here. Instead of using some kind of model binding hook, I went to a action filter. One of the advantages is that the developer can select which actions to use, instead of having this processing for all requests and model bindings (not that it should affect performance that much). Btw action filters can also be applied globally.
Here is my code, first create an action filter.
public class TrimInputStringsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
foreach (var arg in context.ActionArguments.ToList())
{
if (arg.Value is string)
{
if (arg.Value == null)
{
continue;
}
string val = arg.Value as string;
if (!string.IsNullOrEmpty(val))
{
context.ActionArguments[arg.Key] = val.Trim();
}
continue;
}
Type argType = arg.Value.GetType();
if (!argType.IsClass)
{
continue;
}
TrimAllStringsInObject(arg.Value, argType);
}
}
private void TrimAllStringsInObject(object arg, Type argType)
{
var stringProperties = argType.GetProperties()
.Where(p => p.PropertyType == typeof(string));
foreach (var stringProperty in stringProperties)
{
string currentValue = stringProperty.GetValue(arg, null) as string;
if (!string.IsNullOrEmpty(currentValue))
{
stringProperty.SetValue(arg, currentValue.Trim(), null);
}
}
}
}
To use it, either register as global filter or decorate your actions with the TrimInputStrings attribute.
[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
// Some business logic...
return Ok();
}
TrimmingModelBinder is essentially configured for strings only, and defaults back to SimpleTypeModelBinder if it fails, or other binders configured. So if your implementation is essentially the same as in TrimmingModelBinder then it will definitely work for strings only.
For complex types, I recommend creating a new binder, and its corresponding provider, which will have to check all string properties in the model type and trim the value before binding. Then register this binder at index 0 such that its the first one checked before any other binders are tried.
services.AddMvc(options => option.ModelBinderProviders.Insert(0, new MyComplexTypeModelBinderProvider());
Related
I would like to know how we can design .net core api controller to accept an array value like given below
http://localhost:32656/api/Values?str[]="abc"&str[]="xyz"
I did some research online and the only two options, I was able to find is either I need to pass indexes inside the array
eg:- http://localhost:32656/api/Values?str[0]="abc"&str[1]="xyz" (Pass indexes inside the array)
or I need to pass the array as repeated query strings.
eg:- http://localhost:32656/api/Values?str="abc"&str="xyz" (Pass it as repeated query strings)
But I would like to see the other possible options to send an array to .net core 2.1 api controller.
You can take advantage of the FromQuery attribute here, specifying the Name property as str[]:
public IActionResult Values([FromQuery(Name = "str[]")] List<string> strValues)
{
...
}
If you also want to strip out the "s for each value, you can use a Select. Here's a an example:
public IActionResult Values([FromQuery(Name = "str[]")] List<string> strValues)
{
var strValuesWithoutQuotes = strValues.Select(x => x.Trim('"'));
...
}
Here is how we do it:
[Route("api/v1/[controller]")]
public class TestController : Controller
{
[HttpGet]
public async Task Get(List<string> stringValues)
{
...
}
}
Then call the endpoint with http://localhost/api/v1/test?stringValues=string1&stringValues=string2
stringValues should have the list of values in the query string
I'm developing an application with ASP.NET Core WebAPI. What I would like to do is create a filter that I can apply to my controller method that will then let the system know to explicitly convert the incoming JSON object into a specified type.
For example, this is what I envision being able to do:
[HttpPost()]
[MyFilter(typeof(MyType))]
public IActionResult Post(MyType model)
{
....
}
The reason I want to do this is because the incoming JSON object does not match (at all) the structure of "MyType". So I have written a special converter whose logic I can call from this Filter. I want to explicitly state the type and not try to infer it from values within the JSON object (which is, in my case, impossible).
What I would like to do is create a filter that I can apply to my
controller method that will then let the system know to explicitly
convert the incoming JSON object into a specified type.
Filter is not quite right tool for performing custom model binding you described. You should not reinvent the wheels here: custom model binding should be performed by means of (sorry for this pun) custom model binder.
If the only argument against model binder is:
I cannot use CustomModelBinder as that is injected into the pipeline.
then ASP.NET Core provides an elegant way how to apply model binder to specific action arguments, without adding it to the whole pipeline. This could be done via ModelBinder attribute applied to action paramter, which specifies the binder in BinderType property.
Here is a sample:
[HttpPost]
public IActionResult Post([ModelBinder(BinderType = typeof(MyTypeBinder))] MyType model)
{
return Ok();
}
public class MyTypeBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// Put your custom MyType binding code here
}
}
I want to bind an interface model from my action method with a request that the content-type is application/json. I'm using the [FromBody] attribute in my action method.
I tried to create a custom modelBinder derived from ComplexTypeModelBinder, by following this link: Custom Model Binding in Asp.net Core, 3: Model Binding Interfaces, but it doesn't work, my model is always null. I learned after that when you use the atribute [FromBody] the BodyModelBinder is called and internally is calling JsonInputFormatter and it doesn't use the custom modelBinder.
I'm looking for a way to bind my interface model. I can use the MVC DI to map each interface with its implementation. My action method is defined as :
public async Task<IActionResult> Create(IOperator user)
{
if (user == null)
{
return this.BadRequest("The user can't not be null");
}
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
IOperator op = await this.AuthenticationFrontService.CreateOperatorAsync(user.Login, user.Password, user.FirstName, user.LastName, user.ValidUntil, user.Role, user.Comment);
return new CreatedAtActionResult("Get", "operators", new { id = ((Operator)op).Id }, op);
}
I tried another solution by using the MetadataType attribute in my interface but it doesn't exist in the namespace System.ComponentModel.DataAnnotations and i read that asp.net core mvc doesn't use this attribute Asp.Net MVC MetaDataType Attribute not working. I don't want to install the package microsoft.aspnetcore.mvc.dataannotations in domain model project to use the ModelDataType attribute.
I tried another solution by creating a custom JsonInputFormater in other words i derived the class JsonInputFormatter and by analyzing the source code, i've found that the JsonSerializer couldn't deserialize an interface which is logical. So i'm looking for a solution where i could custom the jsonserializer maybe by using a resolver or a generic converter.
Any help will greatly appreciated.
Thanks.
Using an interface is fine for a C# method, but MVC needs to know what concrete type it should instantiate when calling an Action since it's creating it. It doesn't know what type to use, so it can't bind input from Form/QueryString/etc to. Create a very basic model for use in your action that does nothing but implement your interface IOperator, if your goal was to keep it slim, and set that to your Action parameter and it should work fine.
I have tried using an interface on a action as well, and through my own searching, I found no way to get it to work, other than just using classes instead of interfaces to bind to.
public class Operator : IOperator
{
//Implement interface
}
.
public async Task<IActionResult> Create(Operator user)
{
if (user == null)
{
return this.BadRequest("The user can't not be null");
}
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
IOperator op = await this.AuthenticationFrontService.CreateOperatorAsync(user.Login, user.Password, user.FirstName, user.LastName, user.ValidUntil, user.Role, user.Comment);
return new CreatedAtActionResult("Get", "operators", new { id = ((Operator)op).Id }, op);
}
I need to use a custom modelbinder of some kind to always treat incoming dates in UK format, i have setup a custom model binder and registered like so:
GlobalConfiguration.Configuration.BindParameter(typeof(DateTime), new DateTimeModelBinder());
This only seems to work for querystring parameters, and only works if i specify [ModelBinder] on my parameter like so, is there i way to make all actions use my model binder:
public IList<LeadsLeadRowViewModel> Get([ModelBinder]LeadsIndexViewModel inputModel)
Also, how can i get my posted form to my Api controller to use my model binder?
You can register a model binder globally by implementing a ModelBinderProvider and inserting it into the list of services.
Example use in Global.asax:
GlobalConfiguration.Configuration.Services.Insert(typeof(ModelBinderProvider), 0, new Core.Api.ModelBinders.DateTimeModelBinderProvider());
Below is example code demonstrating a ModelBinderProvider and a ModelProvider implemenation that converts DateTime arguments in a culture aware manner (using the current threads culture);
DateTimeModelBinderProvider implementation:
using System;
using System.Web.Http;
using System.Web.Http.ModelBinding;
...
public class DateTimeModelBinderProvider : ModelBinderProvider
{
readonly DateTimeModelBinder binder = new DateTimeModelBinder();
public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
{
if (DateTimeModelBinder.CanBindType(modelType))
{
return binder;
}
return null;
}
}
DateTimeModelBinder implementation:
public class DateTimeModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ValidateBindingContext(bindingContext);
if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName) ||
!CanBindType(bindingContext.ModelType))
{
return false;
}
bindingContext.Model = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName)
.ConvertTo(bindingContext.ModelType, Thread.CurrentThread.CurrentCulture);
bindingContext.ValidationNode.ValidateAllProperties = true;
return true;
}
private static void ValidateBindingContext(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
if (bindingContext.ModelMetadata == null)
{
throw new ArgumentException("ModelMetadata cannot be null", "bindingContext");
}
}
public static bool CanBindType(Type modelType)
{
return modelType == typeof(DateTime) || modelType == typeof(DateTime?);
}
}
I think you don't need a model binder. Your approach is incorrect. The right approach for dates is using a client side globalization library like the globalize library to parse dates formatted in any language and transform them into JavaScript date objects. Then you can serialize your datascript data structures in JSON with the browser JSON.stringify, and this should work.
It is better to use always standard formats for dates and to use a formatter instead than a model binder. Available formatters handle also TimeZones correctly, if you use the kind parameter of your C# DateTime objects to specify if the date time is expressed in local time or in UTC time.
Attribute routing seems to conflict with model binding. If you use attribute routing, you can wrap the global.asax configuration into a single GlobalConfiguration.Config call to avoid the initialisation issues:
GlobalConfiguration.Configure(config =>
{
config.BindParameter(typeof(DateTime), new DateTimeModelBinder());
config.MapHttpAttributeRoutes();
}
(This might be fixed in the upcoming ASP.NET 5 if it's related to bug #1165.)
You do not need a custom model binder for that. You should be good by changing the thread culture to UK or set your web.config settings for UK, if this is what your site is using all the time.
If not, you can still change the DateTimeFormatInfo for the CurrentCulture to UK one.
There is also a good post about model binders available here by Scott Hanselman.
You can put this in Global.asax:
ModelBinders.Binders[typeof(DateTime)] = new DateAndTimeModelBinder()
Looking for some guidance in designing my new MVC 4 app.
I would like to have a url parameter s=2011 on every page of the app to let me know what year of data I'm working with. Obviously, the user will have a way to change that parameter as needed.
I will need that parameter in every controller and wondering the best way to do this. I was thinking of creating a base controller that reads Request.QueryString and puts the year into a public property. However, considering all the extensability points in MVC, I'm wondering if there's a better way to do this?
This very much depends on the design of your app, but just to give you two alternatives
IActionFilter
If you are doing data context per request you can use a global IActionFilter to hook pre-action execution globally and apply a query filter to your data context behind the scenes.
Major down-side of this is that to test the controller you will need to have the full MVC pipeline setup so that the actionfilter gets applied properly.
Dependency Injection
Instead of using sub-classing (base controller as you say) you can use dependency injection . Keeping things more loose will allow you to pull the filter from query string, cookie, user setting in the database or whatever else - without your controller knowing where it comes from.
Here is some pseudo code how I would do it if I was using something like Entity Framework or Nhibernate (also I am sure applicable with other technologies as well)
public Car
{
public string Year { get; set; }
}
public class CarsDataContext : DbContext
{
private IQuerable<Cars> _cars = null;
private Func<Car, bool> _carsFilter = null;
public IQuerable<Car> Cars {
get {
if (_carsFitler != null)
return _cars.Where(_carsFitler);
return _cars;
}
set { _cars = value; }
}
public void ApplyCarsFilter(Func<Car, bool> predicate)
{
_carsFilter = predicate;
}
}
Assuming you have dependency injection setup already (NInject or whichever other framework) in you can configure how the context to be intialized
Bind<CarsDataContext>().ToMethod(() => {
string yearFilter = GetYearFilter(); // can be coming from anywhere
CarsDataContext dataContext = new CarsDataContext();
dataContext.Applyfilter(car => car.Year == yearFilter);
return dataContext;
}).InRequestScope();
Then my controller knows nothing about the data filtering and I can easily test it:
class MyController : Controller
{
public MyController(CarsDataContext dataContext)
{
}
...
}
However I would only do this is filtering the dataset was across many controllers and important part of my software. Otherwise it's pure over-engineering.