What is the best practice for submitting forms in ASP.NET MVC? I have been doing code like this below, but I have a feeling there is a better way.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddNewLink(FormCollection collection_)
{
string url = collection_["url"].ToString();
string description = collection_["description"].ToString();
string tagsString = collection_["tags"].ToString();
string[] tags = tagsString.Replace(" ","").Split(',');
linkRepository.AddLink(url, description, tags);
You can use the parameters directly; the parameters will automatically get parsed and casted to its correct type. The parameter names in the method must match the parameter names that are posted from your form.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddNewLink(string url, string description, string tagsString)
{
string[] tags = tagsString.Replace(" ","").Split(',');
linkRepository.AddLink(url, description, tags);
}
This generally works on more complex objects as well, as long as its properties can be set, and as long as your form keys are in the format objectName.PropertyName. If you need anything more advanced, you should look into model binders.
public class MyObject
{
public int Id {get; set;}
public string Text {get; set;}
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddNewLink(MyObject obj)
{
string[] tags = obj.Text.Replace(" ","").Split(',');
linkRepository.AddLink(url, description, tags);
}
In my opinion, the Model Binder is cleaner. You can learn more at OdeToCode.com
Basically, You wrap your input from a FormCollection to a desirable model as well as validation.
public class LinkModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var link = new Link();
link.Url = GetValue<string>(bindingContext, "url");
// ... and so on for all properties
if (String.IsNullOrEmpty(url.Name))
{
bindingContext.ModelState.AddModelError("Url", "...");
}
return link;
}
private T GetValue<T>(ModelBindingContext bindingContext, string key)
{
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(key, out valueResult);
return (T)valueResult.ConvertTo(typeof(T));
}
}
In the controller
public ActionResult AddNewLink(Link link)
Related
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 passed a list of parameters. Such as "Name", "Id", "Type". There will be an many of these in url, like so:
"Name=blah1,Id=231,Type=blah1;Name=blah2,Id=2221,Type=blah1;Name=blah3,Id=45411,Type=blah3;"
I wonder if there is a way to map these query parameters to a List of objects. So, I can create an object:
MyTestObject {Name;Id;Type} and can say in my controller
Index(IList<MyTestObject> params)
params will be filled in with data from query string.
Something that is similar to http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
I actually followed advice in the article by Mr. Haack I created a class with all of the parameters as public properties. Then I had a view take a list of objects of that type. If the query parameter names follow a certain pattern (prepended by index) then I get a list of my object automatically populated and I don't have to do any manual parsing at all. This is the simplest solution for me.
Example:
query param object:
public class QueryParams
{
public string Id,
public string Name,
public string Type
}
in controller method:
public ActionResult Index(IList<QueryParams> queryData)
then I make sure that query string is formated in the following way(prepended by index):
http://localhost/myapp/?[0].id=123&[0].Name=blah&[0].Type=Person&[1].Id=345&[1].Name=example&[1].Type=Stuff
In my controller, queryData list parameter will contain two objects populated with correct data.
You can you a values provider, and it will populate values from the querystring into a single object. This is what you would do if you're not going to create a View Model.
Transform the QueryString into a FormCollection via:
var GetCollection = new FormCollection( Request.QueryString );
You could create a custom model binder, that works off the Request.QueryString collection, rather than the regular FormCollection.
E.g:
public class MyTestObjectModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var qs = controllerContext.HttpContext.Request.QueryString;
return new MyTestObject
{
Name = qs["Name"],
Id = qs["Id"],
// etc, etc
};
}
}
Then setup your [HttpGet] action accordingly:
[HttpGet]
public ActionResult Index([ModelBinder(typeof(MyTestObjectModelBinder))]MyTestObject m) {
}
You could also register it globally if you like, e.g on Application_Start() :
ModelBinders.Binders.Add(typeof(MyTestObject), new MyTestObjectModelBinder());
Then you just need the model on your action:
[HttpGet]
public ActionResult Index(MyTestObject m) {
}
Having said all of this, if you've got this many parameters, one must ask where do these parameters come from? Most likely a form on another page.
In which case, this should be a [HttpPost] action, with the parameters in the form collection, then the regular MVC model binding will take care of the above code for you.
Yes, ASP.NET MVC could automatically bind collections to action params, but you need to pass your params as a from values, moreover, it is looks like to many params you going pass in query string. Have look at this one http://weblogs.asp.net/nmarun/archive/2010/03/13/asp-net-mvc-2-model-binding-for-a-collection.aspx
Basically what you need to do:
1) Create your class which would contain your params
public class MyParam
{
public int Id {get; set;}
public string Name {get; set;}
//do all the rest
}
2) Create model which you would pass to your view
public class MyViewModel
{
IList<MyParam> MyParams {get; set;}
}
3) Create your collection in your [HttpGet] action and pass that to your view:
[HttpGet]
public virtual ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.MyParams = CreateMyParamsCollection();
return View(model);
}
4) Iterate your collection in the view
#model MyViewModel
#{int index = 0;}
#foreach (MyParam detail in Model.MyParams)
{
#Html.TextBox("MyParams[" + index.ToString() + "].Id", detail.Id)
#Html.TextBox("MyParams[" + index.ToString() + "].Name", detail.Name)
index++;
}
5) Than on your [HttpPost] action you may catch your params in collection
[HttpPost]
public virtual ActionResult Index(MyViewModel model)
or
[HttpPost]
public virtual ActionResult Index(IList<MyParam> model)
P.S
Moreover, if you want to get all your form params in controller you may simple go like that:
[HttpPost]
public virtual ActionResult Index(FormCollection form)
On a related note, I was looking for a way to enumerate through the QueryString name-value collection, and this is what I came up with:
var qry =HttpContext.Request.QueryString;
if (qry.HasKeys())
{
foreach (var key in qry)
{
if(key != null)
var str= String.Format("Key:{0}, value:{1} ", key, qry.Get(key.ToString()));
}
}
This code will give you all the names and their values in the QueryString.
I've looked at most of the ModelBinding examples but can't seem to glean what I'm looking for.
I'd like:
<%= Html.TextBox("User.FirstName") %>
<%= Html.TextBox("User.LastName") %>
to bind to this method on post
public ActionResult Index(UserInputModel input) {}
where UserInputModel is
public class UserInputModel {
public string FirstName {get; set;}
public string LastName {get; set;}
}
The convention is to use the class name sans "InputModel", but I'd like to not have to specify this each time with the BindAttribute, ie:
public ActionResult Index([Bind(Prefix="User")]UserInputModel input) {}
I've tried overriding the DefaultModelBinder but can't seem to find the proper place to inject this tiny bit of functionality.
The ModelName property in the ModelBindingContext object passed to the BindModel function is what you want to set. Here's a model binder that does this:
public class PrefixedModelBinder : DefaultModelBinder
{
public string ModelPrefix
{
get;
set;
}
public PrefixedModelBinder(string modelPrefix)
{
ModelPrefix = modelPrefix;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelName = ModelPrefix;
return base.BindModel(controllerContext, bindingContext);
}
}
Register it in your Application_Start like so:
ModelBinders.Binders.Add(typeof(MyType), new PrefixedModelBinder("Content"));
Now you will no longer to need to add the Bind attribute for types you specify use this model binder!
The BindAttribute can be used at the class level to avoid duplicating it for each instance of the UserInputModel parameter.
======EDIT======
Just dropping the prefix from your form or using the BindAttribute on the view model would be the easiest option, but an alternative would be to register a custom model binder for the UserInputModel type and explicitly looking for the prefix you want.
I was wondering if there was a way to bind form values passed into a controller that have different Id's from the class properties.
The form posts to a controller with Person as a parameter that has a property Name but the actual form textbox has the id of PersonName instead of Name.
How can I bind this correctly?
Don't bother with this, just write a PersonViewModel class that reflects the exact same structure as your form. Then use AutoMapper to convert it to Person.
public class PersonViewModel
{
// Instead of using a static constructor
// a better place to configure mappings
// would be Application_Start in global.asax
static PersonViewModel()
{
Mapper.CreateMap<PersonViewModel, Person>()
.ForMember(
dest => dest.Name,
opt => opt.MapFrom(src => src.PersonName));
}
public string PersonName { get; set; }
}
public ActionResult Index(PersonViewModel personViewModel)
{
Person person = Mapper.Map<PersonViewModel, Person>(personViewModel);
// Do something ...
return View();
}
You could have your own custom model binder for that model.
public class PersonBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext) {
return new Person { Name =
controllerContext.HttpContext.Request.Form["PersonName"] };
}
}
And your action :
public ActionResult myAction([ModelBinder(typeof(PersonBinder))]Person m) {
return View();
}
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)