ASP.NET Web API binding model properties to different request properties - asp.net-web-api

I'm trying to write a custom model binder that can bind a property decorated with an attribute to a differently-named request property e.g.
JSON request
{
"app": "acme"
}
Request model (excerpt)
[Alias("app")]
public string ApplicationName { get; set; }
... should result in ApplicationName being populated with the value 'acme'. I'm getting stuck writing the custom model binder for this:
Model binder
public BindToAliasModelBinder : IModelBinder {
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
...
}
}
Model binder provider
public class BindFromAliasModelBinderProvider : ModelBinderProvider {
public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) {
return new BindFromAliasModelBinder();
}
}
I've registered the provider globally and the binder is being hit as expected. I'm at a loss for what to do next - how do I iterate through the request values and conditionally bind based on the presence of the attribute?

If all you want to do is aliasing, you can use JsonPropertyAttribute, something like [JsonProperty(PropertyName = "app")] on the property.

Related

Register Custom ModelBinder in Asp.NET Core 3.0

Using .NET Framework MVC, one would register a custom model binder like so:
ModelBinders.Binders.Add(typeof(MyModel), new MyModelBinder());
Then any controller action that had a parameter of type MyModel would automatically use MyModelBinder to bind its value.
public ActionResult Test(MyModel o){
// dunski!
...
}
It seems like in .NET Core MVC, one must specify the use of MyModelBinder each time rather than registering it once only - or am I mistaken?
public IActionResult Test([ModelBinder(BinderType = typeof(MyModelBinder))] MyModel o){
...
}
Decorating the type that is being model-bound with the [ModelBinder] attribute will cause any parameter of the same type (in your controller actions) to be automatically bound using the model binder specified in the [ModelBinder] attribute.
Example:
[ModelBinder(BinderType = typeof(MyModelBinder))]
public class MyModel{
public string Name{ get; set; }
public string Age{ get; set; }
}
public class MyModelBinder : IModelBinder{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// whatever model binding you need to do
bindingContext.Result = ModelBindingResult.Success(new MyModel());
return Task.CompletedTask;
}
}
More on this at official documentation

Injecting Non-User-Submitted Data For Use During Validation

From what I can tell, ASP.Net Core performs model state validation before calling the relevant controller action method. This means that code in the action method isn't given an opportunity to add data to the model before it is validated.
What is the ASP.Net Core way of giving a view model access to additional, non-user-submitted data prior to validation?
Example
What I'm trying to do (doesn't work).
The view model's Validate method expects data to be in ValidOptions. However, since validation occurs before the controller can set this property, validation causes the view model to throw an ArgumentNullException.
// From the Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Process([Bind("SelectedId")]ViewModels.Import details)
{
// data needed for validation
details.ValidOptions = await service.ImportTypes.ToListAsync();
if (ModelState.ValidationState != ModelValidationState.Valid) {
// ...
}
}
// From ViewModels.Import
public IEnumerable<Option> ValidOptions { get; set; }
public int SelectdId {get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// throws ArgumentNullException because ValidOptions hasn't been set when this is executed
var option = ValidOptions.Single(t => t.Id == SelectdId);
//...
}
Probably many ways to skin a cat here. But the easiest for you is probably custom model binders. It's a way to "supplement" or change the binding of your model before it hits the controller. I will say that some see it as extremely bad practice to call an external service/repository at the point of model binding, but it does work and can come in handy.
You need to implement a class that inherits from IModelBinder.
public class MyViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
//Bind here. Including calling external services if you want.
}
}
Then you need to implement a provider, this essentially says "when" to bind.
public class MyViewModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyViewModel))
return new MyViewModelBinder();
return null;
}
}
In your configure method of your startup.cs, you need to add the provider to the ModelBinderProviders list.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(config =>
config.ModelBinderProviders.Add(new MyViewModelBinderProvider())
);
}
Further Documentation :
http://dotnetcoretutorials.com/2016/12/28/custom-model-binders-asp-net-core/
http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/
I don't think the official documentation has an article on custom model binders yet unfortunately.

Abort action execution in BindModel method

I have implemented custom model binder in my File Upload Action. Sometimes file upload is dropped by server and BindModel method is called with partial data (ContentLenght and TotalBytes do not match here). I would like to abort Action execution from custom model binder, how to do that?
public class OptionModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var optionModelName = GetOptionModelName(controllerContext);
if (optionModelName != null) return null// !!!How to abort Action execution?!!! here
Trace.TraceInformation(optionModelName);
var model = System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(optionModelName);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
return base.BindModel(controllerContext, bindingContext);
}
public class OptionModelBinderAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new OptionModelBinder();
}
}
[HttpPost]
public ActionResult UploadFile(IEnumerable<HttpPostedFileBase> clientUpload, [OptionModelBinder]IOptionViewModel formData)
{
}
This is not something you want to do from the model binding.
The model binding should not control logic behavior. it does not make sense.
I suggest in the controller, you'll ask if something is null and return the appropriate result to the client.
It is not right to let the model binding do the controller's work.

webapi actionfilters, how to inject a value when using different argument types that inherit from a base type

I have a base request type..
class RequestBase
{
public string inputId;
public string derivedid;
}
and types that inherit ..
class RequestA : RequestBase
{
public string name;
}
and
class RequestB : RequestBase
{
public string color;
}
I have a webapi service, some actions take an input parameter of RequestA, some take RequestB
[HttpPost]
[MyFilter]
[ActionName("Process1")]
public HttpResponseMessage Process1(RequestA request)
{
//do something with request.derivedId
}
[HttpPost]
[MyFilter]
[ActionName("Process2")]
public HttpResponseMessage Process2(RequestB request)
{
//do something with request.derivedId
}
I have an actionfilter that takes the inputId from the request and generates a derivedId
public override void OnActionExecuting(HttpActionContext actionContext)
{
RequestBase request = (RequestBase)actionContext.ActionArguments["request"];
string inputId = request.inputId;
string derivedId = inputId + "123";
// ?? somehow inject derivedId back into the actionContext so that my controller methods can access?
}
As my comment states above, I'd like to populate the derivedId field and have it accessible to my controller methods.
Thanks in advance
There's a few solutions to this problem already described in this thread - one of them should suit you:
ASP.NET MVC Pass object from Custom Action Filter to Action

Force a ASP.NET MVC 3 action parameter to use value from the URL, not object

Consider a model class
public class MyModel
{
public string Id { get; set; }
/* some other properties */
}
And a controller
public class MyController
{
[HttpPut]
public ActionResult Update(string id, MyModel model)
{
/* process */
}
}
The routing is registered as follows:
protected override void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("MyController",
"api/my/{id}",
new { action = "Update", controller = "My"},
new { httpMethod = new HttpMethodConstraint(new[] { "PUT" }) });
}
When using a REST client and sending MyModel serialized as a JSON or XML request to this controller, a null "Id" property of "MyModel", overrides the "id" parameter of the action method, even if you post it to http://api.example.com/api/my/10.
How does one force ASP.NET MVC 3 to populate the "id" property from the URL (in this case "10") and ignore the "Id" property of the "MyModel"?
Note that I'm not using ASP.NET Web API.
Try using attribute [FromUri]. It's in "System.Web.Http". This attribute on action param id indicates it should be bonded using the url request.
using System.Web.Http;//at the top
public class MyController
{
[HttpPut]
public ActionResult Update([FromUri]string id, MyModel model)
{
/* process */
}
}
For MVC3 try to include web-api package(from nuget or manually) to use [FromUri] attribute. IF that is not possible then the only way I can think of getting it is from this.HttpContext.Request.QueryString["id"]
Instead of having id as a action method paramter declare it in action body. May have to change the url query api/my?id=1212. First try using api/my/{id} format.
var id = this.HttpContext.Request.QueryString["id"];

Resources