I've couple of action methods with parameters of IList type.
public ActionResult GetGridData(IList<string> coll)
{
}
The default behavior is when no data are passed to action method the parameter is null.
Is there any way to get an empty collection rather then null application wide ?
Well, you could either do this:
coll = coll ?? new List<string>();
Or you would need to implement a ModelBinder that will create an empty list instead of returning null. E.g.:
public EmptyListModelBinder<T> : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext) ?? new List<T>();
}
}
And wired up as:
ModelBinders.Binders.Add(typeof(IList<string>), new EmptyListModelBinder<string>());
I'd probably stick with the argument check though...
simply do it yourself
public ActionResult GetGridData(IList<string> coll)
{
if(coll == null)
coll = new List<String>();
//Do other stuff
}
Related
For example, a user creates a new question on a forum.
I send ajax to the server, then I use HtmlEncode to exclude the HTML code before saving it in the database.
Is it possible that HtmlEncode would be used automatically when receiving a request?
Also, when using the attribute (for example [HtmlAllowed]) you can allow html code in request.
Thanks
You can achieve it using custom model binder, every string property or string parameter will go through this method when ASP.NET attempts to bind request to parameters of action method
public class StringBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
var str = (string)value?.ConvertTo(typeof(string));
str = HttpUtility.HtmlEncode(str);
return str;
}
}
And in Application_Start()
ModelBinders.Binders.Add(typeof(string), new StringBinder());
Thanks to Yegor Androsov, for pointing me to right direction.
This ModelBinder automaticaly encode all string properties, except that has [SafeHtml] attribute
public class SafeStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
string name = bindingContext.ModelName;
string value = request.Unvalidated[name];
Type holderType = bindingContext.ModelMetadata.ContainerType;
if (holderType != null)
{
PropertyInfo propertyType = holderType.GetProperty(bindingContext.ModelMetadata.PropertyName);
if (propertyType == null) return value;
object[] attributes = propertyType.GetCustomAttributes(true);
bool hasAttribute = attributes.Cast<Attribute>().Any(a => a.GetType() == typeof (SafeHtmlAttribute));
if (!hasAttribute && !string.IsNullOrEmpty(value))
{
value = HttpUtility.HtmlEncode(value);
}
}
return value;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class SafeHtmlAttribute : Attribute { }
I'm working on my project in MVC 3 and searching for a way, which can add this functionality to all my Html.TextboxFor:
When user type "foo" and submit form, in controller level I get it by model as "fuu" for example.
I need this feature to replace some Unicode characters by some others.
Let I show my code in View and Controller:
View:
#Html.TextBoxFor(model => model.Title) // user will type "foo", in TitleTexbox!
Controller:
[HttpPost]
public virtual ActionResult Create(MyModel model)
{
var x = model.Title;
//I need variable x have 'fuu' instead of 'foo', replaceing "o" by "u"
//...
}
Should I write an override for Html.TextboxFor?
as i understood from your code , you expect from your model to be ready(processed) when it passed to your controller action.and for accomplishing this the only way is using model-binding.
but this approach is limited to particular type/class/model/viewmodel or whatever you name it.
you can create your own modelBinder as:
public class MyCustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var myModel= (MyModel ) base.BindModel(controllerContext, bindingContext) ?? new MyModel ();
myModel.Title.Replace('o','u');
return myModel;
}
}
and then you most register your Custom Model Binder in Global.asax
ModelBinders.Binders.Add(typeof(MyModel),new MyCustomModelBinder());
make change in your action like this:
[HttpPost]
public virtual ActionResult Create([ModelBinder(typeof(MyCustomModelBinder))] MyModel model)
{
var x = model.Title;
//here you will have the modified version of your model
//...
}
good luck.
I would like to be able to post any serialized object to an action method and instantiate a new object of the posted type in order to use TryUpdateModel. They didn't teach me any of this stuff in the QBasic help file... How can I instantiate the unknown type based on the posted data?
If it would help, I could theoretically include the name of the type as a string in the posted data. I was hoping to avoid that because it seemed like I would need the full name of the type.
public void Save(object/dynamic whatever, string typename) {
//Instantiate posted type
//TryUpdateModel
context.Entry(Thing).State = EntityState.Modified;
context.SaveChanges();
}
Here is an example of a serialized object
Thing.Id=1&Thing.Name=blah&Thing.OptionID=1&Thing.ListItems.index=1&Thing.ListItems%5B1%5D.Id=1&Thing.ListItems%5B1%5D.Name=whatever&Thing.ListItems%5B1%5D.OptionID=2&Thing.ListItems%5B1%5D.ThingID=1&Thing.ListItems%5B1%5D.EntityState=16
From Fiddler
Thing.Id 1
Thing.Name blah
Thing.OptionID 1
Thing.ListItems.index 1
Thing.ListItems[1].Id 1
Thing.ListItems[1].Name whatever
Thing.ListItems[1].OptionID 2
Thing.ListItems[1].ThingID 1
Thing.ListItems[1].EntityState 16
You could write a custom model binder which uses reflection and the typeName parameter:
public class MyModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue("typename");
if (typeValue == null)
{
throw new Exception("Impossible to instantiate a model. The \"typeName\" query string parameter was not provided.");
}
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
and then simply:
[HttpPost]
public ActionResult Save([ModelBinder(typeof(MyModelBinder))] object model)
{
context.Entry(model).State = EntityState.Modified;
context.SaveChanges();
return View();
}
I've found a lot of information on implementing a custom model binder for validation purposes but I haven't seen much about what I'm attempting to do.
I want to be able to manipulate the values that the model binder is going to set based on attributes on the property in the view model. For instance:
public class FooViewModel : ViewModel
{
[AddBar]
public string Name { get; set; }
}
AddBar is just
public class AddBarAttribute : System.Attribute
{
}
I've not been able to find a clean way to find the attributes on a view model property in the custom model binder's BindModel method. This works but it feels like there should be a simpler solution:
public class FooBarModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var hasBarAttribute = false;
if(bindingContext.ModelMetadata.ContainerType != null)
{
var property = bindingContext.ModelMetadata.ContainerType.GetProperties()
.Where(x => x.Name == bindingContext.ModelMetadata.PropertyName).FirstOrDefault();
hasBarAttribute = property != null && property.GetCustomAttributes(true).Where(x => x.GetType() == typeof(AddBarAttribute)).Count() > 0;
}
if(value.GetType() == typeof(String) && hasBarAttribute)
value = ((string)value) + "Bar";
return value;
}
}
Is there a cleaner way to view the attributes on the view model property or a different kind of attribute I could be using? The DataAnnotation attributes really seem to be for a different problem.
UPDATE
Craig's answer got me to the right place but I thought I'd put some examples in here for others.
The metadata provider I ended up with looks like
public class FooBarModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metaData = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if(attributes.OfType<AddBarAttribute>().Any())
metaData.AdditionalValues.Add("AddBarKey", true);
return metaData;
}
}
The model binder looks like:
public class FooBarModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
if(bindingContext.ModelMetadata.AdditionalValues.ContainsKey("AddBarKey"))
value = ((string)value) + "Bar";
return value;
}
}
The "correct" way (per the guy who wrote it) is to write a model metadata provider. There's an example at the link. Not precisely "simple," but it works, and you'll be doing what the rest of MVC does.
I got a custom ModelBinder and i would like to get the action. Because i want to get the Attributes of the action using reflection, the action name is not enough.
my action method:
[MyAttribute]
public ActionResult Index([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
}
and here a typically ModelBinder
public class MyModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// here i would like to get the action method and his "MyAttribute"
}
}
any suggestions, other solutions ?
many thanks in advance
No, you cannot with 100% certainty get the current action from a model binder. The model binder is not coupled to the action, but to binding to a model. For example, you can call
TryUpdateMode(model)
In an filter before an action has been chosen. Also note that an action method might not even be a CLR method (see http://haacked.com/archive/2009/02/17/aspnetmvc-ironruby-with-filters.aspx) that can be reflected on.
I think the real question is, what exactly are you trying to accomplish and is this the right way? If you want information from the action to be passed to the model binder (heeding the advice that your model binder should degrade gracefully if the information isn't there), you should use an action filter to put the information in HttpContext.Items (or somewhere like that) and then have your binder retrieve it.
An action filter's OnActionExecuting method receives an ActionExecutingContext which has an ActionDescriptor. You can call GetCustomAttributes on that.
You could try this:
var actionName = controllerContext.RouteData.GetRequiredString("action");
var myAttribute = (MyAttribute) Attribute.GetCustomAttribute(controllerContext.Controller.GetMethod(actionName), typeof(MyAttribute));
You could override ControllerActionInvoker.FindAction() to get the action's attribute and store it in HttpContext.Current.Items as mentioned here, or extendedControllerContext.RequestContext, as follows:
public class MyControllerActionInvoker : ControllerActionInvoker
{
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
var action = base.FindAction(controllerContext, controllerDescriptor, actionName);
if (action != null)
{
var requestContext = ExtendedRequestContext.Bind(controllerContext);
var attr = action.GetCustomAttributes(typeof(MyAttribute), false).FirstOrDefault();
if (attr != null)
requestContext.CustomAttribute = (MyAttribute)attr;
}
return action;
}
}
public class ExtendedRequestContext : RequestContext
{
public MyAttribute CustomAttribute { get; set; }
public static ExtendedRequestContext Bind(ControllerContext controllerContext)
{
var requestContext = new ExtendedRequestContext
{
HttpContext = controllerContext.RequestContext.HttpContext,
RouteData = controllerContext.RequestContext.RouteData
};
controllerContext.RequestContext = requestContext;
return requestContext;
}
}
The default action invoker is replaced either in your controller's constructor or in a custom controllers factory:
public MyController() : base()
{
ActionInvoker = new MyControllerActionInvoker();
}
By the way, Controller.TempData already contains an item of ReflectedParameterDescriptor type, which gives you access to ActionDescriptor, so the above code may be redundant. However, beware this is implementation specific, so may change over time.
Finally, get the attribute from that storage in your binder class:
var requestContext = (ExtendedRequestContext)controllerContext.RequestContext;
if (requestContext.CustomAttribute != null)
{
// apply your logic here
}