I have a view model
public class ViewModel
{
public string Text { get; set; }
public string Name { get; set; }
}
The submited form provides only the Text value. I'd like to set the the Name property in my custom model binder.
So I derived my custom model binder from the DefaultModelBinder class and overrided the BindModel method.
The problem is that the BindModel method is called only for the incomning properties.
My question is how can I set the Name value in my cystom model binder ?
If you do not have an incoming value for Name, then you are not doing (custom) model binding. Instead, you want to supply some data in your model object before the action executes, right ? If so, use ActionFilter for it, override OnActionExecuting() and supply the data you need into action parameters.
public class SupplyNameAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionParameters != null)
{
foreach (KeyValuePair<string, object> parameter in filterContext.ActionParameters)
{
if (parameter.Key == "Name") parameter.Value == "Hey";
}
}
}
}
EDIT :
You can also use custom ValueProvider for default model binding, see
http://mgolchin.net/posts/19/dive-deep-into-mvc-ivalueprovider
Related
I have created the following type in my MVC 6 application:
public class EncryptedType
{
...
}
I have a controller method as follows:
public IActionResult Index(EncryptedType id)
{
...
}
So given the the url would be something like:
http://localhost/Area1/Controller1/Index/fgf23237dsd
Where the EncryptedType class can handle converting to/from a string.
Currently the id is defaulting to the parameterless constructor value. What do I need to do to make this automatically convert the string id on the url to an instance?
Naturally I could use a string but I just feel having an explicit type to represent the type in the method is more explicit.
This should work out of the box.
For example, if this is EncryptedType:
public class EncryptedType
{
public string Id {get; set;}
public string Name {get; set;}
}
Then, if your query string looks something like this:
http://localhost:5000/Index?Id=1&Name=MyName
The data from the query string will be parsed automatically to an instance of this class.
It is not possible to instansiate the class with an other constructor than the default one.
Please look at the docs:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding
There is a section there that states the following:
In order for binding to happen the class must have a public default
constructor and member to be bound must be public writable properties.
When model binding happens the class will only be instantiated using
the public default constructor, then the properties can be set.
OK, I guess today I have my googling head on, so was able to work through a number of Stack Overflows and Microsoft docs. There are essentially 4 parts to wire up. These are:
IModelBinder implementation
IModelBinderProvider implementation
Mvc Service Registration Options
Attribute on the Controller Action
IModelBinder implementation
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class EncryptedTypeModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (bindingContext.ModelType == typeof(EncryptedType))
{
EncryptedType decodedEncryptedTypeParameter;
ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
string theStringToConvertToEncryptedType = value.FirstValue;
// add the custom convert from string to your type here, and set on the bindingContext.Result. We still return
// this value from the method wrapped in a Task.
if (EncryptedType.TryParse(theStringToConvertToEncryptedType, out decodedEncryptedTypeParameter))
{
bindingContext.Result = ModelBindingResult.Success(decodedEncryptedTypeParameter);
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.FromResult(bindingContext.Result);
}
return Task.FromResult(ModelBindingResult.Failed());
}
}
IModelBinderProvider implementation
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class EncryptedTypeModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
return new EncryptedTypeModelBinder();
}
}
Mvc Service Registration Options
services.AddMvc().AddMvcOptions(a =>
a.ModelBinderProviders.Add(new EncryptedTypeModelBinderProvider()));
Attribute on the Controller Action
public IActionResult Index(
[ModelBinder(BinderType = typeof(EncryptedTypeModelBinder))] EncryptedType id)
I know how to create a model class that mirrors query string variables so that when it comes into my Web API controller action, the model is populated.
However, is there a way to make it so that I'm not locked into the query string variable names as the properties on my model class?
Example:
public class MyModel {
public string o {get;set;}
}
public class MyController {
public string Get(MyModel model) {
}
}
Then, if my query string looks like:
GET http://domain.com/?o=12345
Is there a way to name that model property "Order" or something instead of "o" and then have it populated with the value from "o="?
You can create custom model binder that will bind data to model as you wish. To use it you should:
public string Get([ModelBinder(typeof(MyComplexTypeModelBinder))]MyModel model)
{
...
}
To create custom model binder you can inherit from IModelBinder or from DefaultModelBinder.
public class MyComplexTypeModelBinder : IModelBinder
{
public Object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException("bindingContext");
// Create the model instance (using the ctor you like best)
var obj = new MyComplexType();
// Set properties reading values from registered value providers
obj.Order = FromPostedData<string>(bindingContext, "o");
...
return obj;
}
private T FromPostedData<T>(ModelBindingContext context, String key)
{
// Get the value from any of the input collections
ValueProviderResult result;
context.ValueProvider.TryGetValue(key, out result);
// Set the state of the model property resulting from
context.ModelState.SetModelValue(key, result);
// Return the value converted (if possible) to the target type
return (T) result.ConvertTo(typeof(T));
}
Solution for this scenario is custom IValueProvider. This ASP.NET MVC extension point is the correct place, where we can bridge the QueryString keys into Model.Property names. In comparison with ModelBinder, this will target exactly what we need (while not introducing later issues, when even other value providers (FORM) accidently contains that key...)
There is good tutorial how to introduce the custom IValueProvider:
http://donovanbrown.com/post/How-to-create-a-custom-Value-Provider-for-MVC.aspx
And there is an simple example which is able to provide values for Model "Order" property, coming as QueryString "o" key:
Factory
// Factory
public class MyValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext ctx)
{
return new MyValueProvider(ctx);
}
}
Provider
// Provider
class MyValueProvider : IValueProvider
{
protected HttpRequestBase Request { get; set; }
public MyValueProvider(ControllerContext ctx)
{
Request = ctx.HttpContext.Request;
}
// our custom logic to test QueryString keys, and expected prefixes
public bool ContainsPrefix(string prefix)
{
var containsSpecial =
"Order".Equals(prefix, StringComparison.OrdinalIgnoreCase)
&& Request.QueryString.AllKeys.Contains("o"
, StringComparer.InvariantCultureIgnoreCase);
return containsSpecial;
}
// Handling "Order" key
public ValueProviderResult GetValue(string key)
{
if (!ContainsPrefix(key))
{
return null;
}
var values = Request.QueryString.GetValues("o");
if (values.Any())
{
return new ValueProviderResult(values, values.First()
, CultureInfo.CurrentCulture);
}
return null;
}
}
And in the global.asax we have to inject it:
protected void Application_Start()
{
ValueProviderFactories.Factories.Add(new MyValueProviderFactory());
...
Can I create an attribute that will let me modify the value of it in my ASP.NET MVC Model? It relates to this question below where '%' is being sent to the database, but I would like a generic way to escape certain characters with the data comes from the UI. I know you can validate properties, but can you modify them on the SET?
MySQL and LIKE comparison with %
[Clean]
public string FirstName { get; set; }
[Clean]
public string LastName{ get; set; }
Does this have a lot of value over just calling a clean method in the setter for each property? I worry that even if this were possible, it would introduce a lot of complexity depending on what the expected behavior was.
My suggestion is to just make a function and call it from the setter instead.
I think your Attribute should be at the class level to get access to this class properties
Lets say :
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class ClearAttribute : ValidationAttribute
{
private string[] wantedProperties;
public ClearAttribute(params string[] properties)
{
wantedProperties = properties;
}
public override object TypeId
{
get { return new object(); }
}
public override bool IsValid(object value)
{
PropertyInfo[] properties = value.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (wantedProperties.Contains(property.Name))
{
var oldValue = property.GetValue(value, null).ToString();
var newValue = oldValue + "Anything you want because i don't know a lot about your case";
property.SetValue(value, newValue, null);
}
}
return true;
}
}
And the usage should be:
[Clear("First")]
public class TestMe{
public string First {get; set;}
public string Second {get; set;}
}
Hope this helped :)
All you have to do is create a Custom Model Binder and override the SetProperty method to do the clean up.
public class CustomModelBinder: DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.Attributes.Contains(new Clean()) && propertyDescriptor.PropertyType == typeof(string))
{
value = value != null ? ((string)value).Replace("%", "") : value;
}
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
}
}
You can employ any of these options to use your custom model binder.
Registering the custom binder for a particular model in Global.asax.cs
ModelBinders.Binders.Add(typeof(MyModel), new CustomModelBinder());
Registering the custom binder in action parameter
public ActionResult Save([ModelBinder(typeof(CustomModelBinder))]MyModel myModel)
{
}
Registering the custom binder as the default model binder.
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
I'm using ValueInjecter to flatten/unflatten view models into domain objects created by Entity Framework (4.3.1) model-first. All of my VARCHAR columns in my database are NOT NULL DEFAULT '' (personal preference, no desire to open up a holy war here). On post, the view model comes back with any string property that has no value as null, so when I attempt to inject it back into my domain model class, EF barks at me for attempting to set a property with IsNullable=false to null. Example (over-simple):
public class ThingViewModel
{
public int ThingId{get;set;}
public string Name{get;set;}
}
public class Thing
{
public global::System.Int32 ThingId
{
//omitted for brevity
}
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String Name
{
//omitted for brevity
}
}
Then, my controller post looks like this:
[HttpPost]
public ActionResult Edit(ThingViewModel thing)
{
var dbThing = _thingRepo.GetThing(thing.ThingId);
//if thing.Name is null, this bombs
dbThing.InjectFrom<UnflatLoopValueInjection>(thing);
_thingRepo.Save();
return View(thing);
}
I'm using UnflatLoopValueInjection because I have nested types in the actual domain version of Thing. I attempted to write a custom ConventionInjection to convert null strings to string.Empty, but it appears that UnflatLoopValueInjection switches it back to null. Is there a way I can get ValueInjecter not to do this?
Nuts, I just figured it out with help from the wiki. The solution appears to be to extend UnflatLoopValueInjection:
public class NullStringUnflatLoopValueInjection : UnflatLoopValueInjection<string, string>
{
protected override string SetValue(string sourceValue)
{
return sourceValue ?? string.Empty;
}
}
I have a View for creating a customer that contains numerous textboxes. After the user tabs out of each textbox I want to use JQuery to call a Controller method that will check in the DataBase and look for any possible matches, the controller will then send content and I will use jQuery to dynamically show the possible matches (Similar to what Stack Overflow does when you enter in your question and shows Related Questions).
My question is, I have 15 textboxes and would like to send that data from each back with each call. I'd like to avoid having my Controller method with a signature like
Public ActionResult CheckMatches(string param1, string param2... string param15)
Is there an easier way to pass multiple paramers as a single object, like FormCollection?
All you need to do is create a type with properties the same name as the names of your textboxes:
public class CheckMatchesAguments
{
public string Param1 { get; set; }
public string Param2 { get; set; }
// etc.
}
Then change your action to:
public ActionResult CheckMatches(CheckMatchesAguments arguments)
That's all!
Be warned, though: If CheckMatchesAguments has any non-nullable properties (e.g., ints), then values for those properties must be in the FormCollection, or the default model binder won't bind anything in the type. To fix this, either include those properties, too, in the form, or make the properties nullable.
Javascript:
var data = { foo: "fizz", bar: "buzz" };
$.get("urlOfAction", data, callback, "html")
Action:
public ActionResult FooAction(MegaParameterWithLotOfStuff param)
And a custom model binder:
public class MegaParameterWithLotOfStuffBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext,
string modelName, Type modelType,
ModelStateDictionary modelState)
{
var param = new MegaParameterWithLotOfStuff();
param.Foo = controllerContext.
HttpContext.Request.Form["foo"];
param.Bar = controllerContext.
HttpContext.Request.Form["bar"];
return customer;
}
}
Global.asax:
protected void Application_Start()
{
ModelBinders.Binders[typeof(MegaParameterWithLotOfStuff)] =
new MegaParameterWithLotOfStuffBinder();
}
Or binding filter+action combo:
public class BindMegaParamAttribute: ActionFilterAttribute
{
public override void OnActionExecuting
(ActionExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
var param = new MegaParameterWithLotOfStuff();
param.Foo = httpContext.Request.Form["foo"];
param.Bar = httpContext.Request.Form["bar"];
filterContext.ActionParameters["param"] = param;
base.OnActionExecuted(filterContext);
}
}
Action:
[BindMegaParam]
public ActionResult FooAction(MegaParameterWithLotOfStuff param)