Call custom ModelBinder in .NET Core - c#

I'm creating a custom ModelBinder in .NET Core 1.1.0, and I think I have it mostly figured out. I want to use this binder only when I specifically want to, but I can't figure out how to accomplish this. I want this model binder to be ignored when I don't call it, but when I call it I want all others to be ignored. How can I accomplish this?
The two things that seem feasible are the parameter attributes [Bind] and [ModelBinder], but neither of these really works.
I first tried the below:
[HttpGet]
public async Task<IActionResult> Get([Bind("test")] int userId)
{
// stuff
}
When my custom IModelBinderProvider is hit, the ModelBinderProviderContext contains a BindingInfo property, which in turn contains an IPropertyFilterProvider. When debugging, that filter provider contains a collection called Include with my value of test. There doesn't appear to be any way to check for that programmatically, however--there's no way that I can find to actually access that collection. It's null if nothing is set, so I could hypothetically check for null, but that's very messy and isn't a good idea.
To illustrate, here's the debugger info for the ModelBinderProviderContext:
Next, I tried using this code:
[HttpGet]
public async Task<IActionResult> Get(
[ModelBinder(BinderType = typeof(MyModelBinder))] int userId
)
{
// stuff
}
This attribute appears to have no effect whatsoever. It does not force MyModelBinder to be used; model binders are used in the order specified in Startup.cs (the list in MvcOptions).

The PropertyFilterProvider contains a BindAttribute instance which implements the IPropertyFilterProvider interface. You could cast the instance to a BindAttribute and access the Include property:
var bindAttribute = context.BindingInfo.PropertyFilterProvider as BindAttribute;
var include = bindAttribute?.Include;
Keep in mind that the cast may not succeed, resulting in bindingAttribute being null.

Related

Why applying custom model binder change default binding to QueryString in .Net Core?

I have a data class which i use to get data from body, something like:
public class FilterDto<T>{
//some general properties
}
and I used them normally in controller, and the input is recognized as FromBody by default:
[HttpPost]
[Microsoft.AspNetCore.Mvc.Route("group/list")]
[ProducesResponseType(typeof(ApiResult<Paginable<BannerWidgetGroupListDto>>), 200)]
public async Task<Paginable<BannerWidgetGroupListDto>> GetBannerWidgetGroupList(FilterDto<BannerWidgetFilterDto> filter)
{
//do stuff
}
I write a custom modelBinder to normalize string and assign it directly to the Filter mode:
[ModelBinder(BinderType = typeof(StringNormalizerBinder))]
public class FilterDto<T>{
//some general properties
}
after this in every API that FilterDto is used, I should pass it as a query string, not the body.
if I add [FromBody] manually it works fine and the normalizer is applied, but I can not find out why the default behavior is overridden?
I have read the MSDN doc and checked some articles but no mention of this phenomenon.
any ideas what caused this? or where should I check?

Handling partial updates (under-posting) with AutoMapper

I'm trying to find a method of handling partial updates; A ViewModel has 5 fields, but only one field is sent in the Request (i.e. only send fields that were modified instead of posing the entire view model), since null values were not explicitly sent for the other fields, I don't think that they should be set to null. Since AutoMapper does not have any kind of support for a Bind list, it's proving difficult to find an elegant solution...
public virtual IActionResult Edit(int id, ViewModel1 viewModel)
{
var model = GetModel(id);
mapper.Map(viewModel, model);
// any field that was not posted, but exists in the ViewModel1 is now null in the model
...
}
The only approach I can think of, is to use Request.Form.Keys and Reflection to build an ExpandoObject that only contains the posted properties, then set CreateMissingTypeMaps and ValidateInlineMaps to allow AutoMapper to map the dynamic types... It just feels like this is a dirty workaround to compensate for functionality that's missing in AutoMapper... Is there a standard way of handling this?

How to keep ASP.NET binding engine from needlessly invoking read-only properties when constructing class object?

We have a web application using ASP.NET MVC, which supports controller methods that take a class object as a parameter. There is automatic binding of the posted form values in order to construct and populate the class object, which occurs before the actual code of the controller method is even invoked. So far so good, but here is my problem: in the course of constructing the class object, the ASP.NET binding engine is invoking every public property of the class. Some of these properties involve some expensive calculation (iterating internal lists, doing counts and so forth), and it is irritating to have them called for no reason whatsoever and the values thrown away. These are read-only properties with only a 'get' and no 'set', so the binder cannot possibly be touching them for purposes of assigning values to them. How can we keep this from happening?
Here is what has been tried so far without success:
Use a [Bind(Include = "...")] in the controller method declaration to limit to the other (non-read-only) properties that can actually be assigned to.
Use a [BindNever] annotation on the read-only properties within the class definition.
Our current workaround is, we just abandon the read-only property implementation altogether and rewrite them all as methods. The binder code does not invoke methods, so this works, but we would still rather have them as properties, and it seems like a problem that should be capable of solution. Any thoughts anyone?
== EDIT =============
Additional things tried, in response to answers here, that still did not work:
Use a [Bind(Exclude = "...")] in the controller method declaration specifying the properties we do not want to invoke. (They are still invoked anyway.)
== EDIT 2 =============
Additional details per request. I am using VS 2015, .NET Framework 4.5.2. Just now I created a sample program to demonstrate the problem:
File -> New -> Project -> Web -> ASP.NET Web Application
Under "ASP.NET 4.5.2 Templates", choose "MVC"
In ManageViewModels.cs, there is a class called "AddPhoneNumberViewModel". This class occurs as a parameter of the method ManageController.AddPhoneNumber (HttpPost version). Add a public property to the class called "PropertyThatShouldNeverBeCalled" and place a breakpoint within it (see code sample below).
Compile and run the application in Debug mode. Attempt to access the endpoint /Manage/AddPhoneNumber, you will have to create an account, then access the endpoint again, enter a phone number, and click "Submit".
Observe that you have hit your breakpoint in PropertyThatShouldNeverBeCalled.
Try one of the unsuccessful fixes described above (e.g. add [Bind(Exclude="PropertyThatShouldNeverBeCalled")] to the definition of ManageController.AddPhoneNumber).
Repeat step 4 above. Observe that you have still hit your breakpoint.
Code sample 1 (from ManageViewModel.cs):
public class AddPhoneNumberViewModel
{
[Required]
[Phone]
[Display(Name = "Phone Number")]
public string Number { get; set; }
public bool PropertyThatShouldNeverBeCalled
{
get
{
bool returnVal = true; // place breakpoint here
return returnVal;
}
}
}
Code sample 2 (from ManageController.cs):
//
// POST: /Manage/AddPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber([Bind(Exclude = "PropertyThatShouldNeverBeCalled")]AddPhoneNumberViewModel model)
{
// etc.
}
P.S. The problem also occurs when the project is compiled in release mode and run without debugging. I have used logging to confirm this in my original project. For purposes of the sample project, it is just simpler to observe the problem using a breakpoint.
You should use a POCO model such as a DTO (Domain Transfer Object) or a View Model that only contains the properties you need to bind (with any validation you want to have ASP.NET handle as well) and then feed that model into the rest of your application to consume as needed.
You can use the [Bind(Exclude="...")] attribute on the object type as the parameter to the Controller:
Example:
[HttpPost]
public ActionResult Post([Bind(Exclude = "PropertyName")] MyClass dataIn)
{
...
}
MyClass being the object that is being bound on the data coming in to the Controller Action. The Exclude= accepts a comma delimited string for all properties you do not want to be part of the model binding.
https://msdn.microsoft.com/en-us/library/system.web.mvc.bindattribute.exclude(v=vs.118).aspx

How can I collect model validation error messages?

I am building an ASP.NET MVC application, and I am trying to find a way where I can collect data from the user in a custom view model, try and set these values to one or more of my entities, then based on validation logic on those entities, collect error messages if any and get them back to the view. I am new to MVC and web design in general, it is therefore quite possible that I am making major conceptual errors, but I have tried to research as far as I could.
I realize that this is more work than having the view be strongly typed to the entity, where it would then be easy to have the validation errors display, as in this tutorial. However, I don't want to do this for security and because there are some places where I want to have values collected from a single view model to be set in multiple different entities.
I also realize that I could set validation rules on the view model itself, rather then on the entity, but this seems like poor architecture, as I would have to define them redundantly in different view models, and I would then be less sure whether I had prevented bad values from being persisted to the database.
My plan is therefore to have the validation rules be set on the entity itself and to have the view model as a dumb container. Then, in a different location in the application, I would apply the values from the view model to my entity(ies) in accordance my business logic. At this point, I would like my validation logic to be called. If the data is invalid, I plan on setting the error string in the custom attribute on the view model to the error from the validation logic on the entity. I am thinking it would go something like this:
public class CustomViewModel()
{
[SomeCustomValidation()] //Has a place for an error string and a boolean IsValid
String Property { get; set; }
}
public class BusinessLogic()
{
CustomViewModel TestForValidity(CustomViewModel viewModel)
{
MyEntity.Property = viewModel.Property;
// if(MyEntity.IsValid)? catch SomeSortOfException?
// collect error message, put it in the attribute on the view model, set IsValid to false
}
}
public class MyEntity()
{
[MoreCustomValidation()]
public String Property { get; set; }
}
I therefore have three questions:
When I try to pass data that does not satisfy my validation rules, will some sort of error or exception be thrown? Is there some indication I can use or collect when I try to pass in invalid data?
If there is some error or exception thrown, how can I collect the error message so I can assign it to my view model?
Most importantly, am I going about this all wrong? Can attributes not be modified at runtime, for example to include a new error message or to change IsValid to false? I know that I can use reflection to access the attributes. If I can modify them, how would I do so?
Thank you in advance for your help. I apologize if I misunderstand something big.
It seems you might be over-complicating things a bit. I think what you want to do is prevent the model binder from binding to properties that it should not be able to, as well as retaining the ability to check ModelState.IsValid when properties on your model do not meet the requirements of their validation attributes.
IMO the best way to accomplish this is through the use of what I call "strongly-typed binding filters". First define an interface with only the properties that you want the model binder to be allowed to bind on your model.
public interface INewBlogPost
{
string Title { get; set; }
string Body { get; set; }
}
Then ensure your entity inherits from the binding filter interface.
public class BlogPost : INewBlogPost
{
...
}
Next, modify your action method to create a new entity and manually invoke the model binder whilst typing it to the interface you just defined.
public ActionMethod NewBlogPost()
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost);
if (ModelState.IsValid)
{
...
}
}
Because you passed in a type when invoking the model binder via TryUpdateModel you explicitly told the model binder what type to bind to. This means that the model binder will only have access to the properties listed in the interface. Now when you pass a model into the method to be bound it will have to be of type INewBlogPost. Because your entity inherits from your binding filter interface, an instance of it will satisfy this requirement. The model binder will happily bind to the properties on the interface completely oblivious of any other properties your model object may have.
See this blog post for more information.
Aside
It is sometimes easy to run into action method ambiguity when you have two action methods with the same name; one for POST and one for GET like this:
[HttpGet]
public ActionResult NewBlogPost()
{
return View();
}
[HttpPost]
public ActionResult NewBlogPost()
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost);
if (ModelState.IsValid) { ... }
}
An easy way to fix that is to modify your POST action method to look like this:
[HttpPost]
public ActionResult NewBlogPost(FormCollection formCollection)
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost, formCollection);
if (ModelState.IsValid) { ... }
}
The MVC model binder knows how to bind the request form collection to an argument of type FormCollection so it will populate this just fine. Because your POST action now accepts an argument, it is no longer ambiguous with your GET method. You can pass this formCollection into TryUpdateModel to be used as the binding source if you wish, but you don't have to as it will default to the request form collection anyway. But since you are passing it in you may as well use it :)

Routing by posted content type in ASP.NET MVC

I have a fixedURL to which I'd like to post different types of xml message, deserialized using DataContracts. Depending on the type of the deserialized message, I'd like to route to:
overloaded methods, e.g.
void Process(ContractType1 request) {}
void Process(ContractType2 request) {}
So at some point I need to deserialize this message and hopefully allow the default routing rules to match the correct method. Which extensibility point should I use for this? Or even better, can I make this work out of the box?!
If it makes any difference, I'm using MVC 3.
ASP NET MVC does not respect the overload if they are not decorated for different HTTP methods - e.g. one for POST, other for GET.
You need to use [ActionName(Name = "Process2")] to change the route name. And you will have to use different routes to access (if the HTTP methods are the same)
Have a look here.
Apart from the technical workaround, passing different contracts to the same URL is against the REST principles. Data could be in different format (XML, JSON, ...) but it must be the same. The URI defines a unique intention. Now it is possible to have a common dumpster where documents are all dumped to the same URI but then ASP NET MVC default model binder would not be able to help you and you need to create your own model binder.
Contrary to the other answer I say this is possible
Asp.net MVC is a great platform that can be easily extended. And so basically I've written a special action method selector that makes it possible to write overloads that can serve the same HTTP method but defer in parameters. By default you'd get runtime error that action method can't be resolved. But when you use this action method selector you get rid of this error.
Basically if your parameter classes have distinct parameter names, you can actually select methods by that.
Action method selector is called RequiresRouteValuesAttribute and a typical usage scenario would be with default route where id is optional in:
{controller}/{action}/{id}
This means that you either have to write
public ActionResult Index(int? id)
{
if (id.HasValue)
{
// display details view
}
else
{
// display master view
}
}
but by using my action method selector you can easily write two action methods:
public ActionResult Index()
{
// display master view
}
[RequiresRouteValues("id")]
public ActionResult Index(int id)
{
// display details view
}
The same could be applied to your action methods as long as your custom types have distinct property names or methods use different parameter names. So in your case it could be something like:
[RequiresRouteValues("first.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType1.Distict.Property.Name")]
public ActionResult Process(ContractType1 first)
{
// do what's appropriate
}
[RequiresRouteValues("second.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType2.Distict.Property.Name")]
public ActionResult Process(ContractType2 second)
{
// do what's appropriate
}
Read all the details about this action method selector and get the code as well.

Categories