I'm trying to create a wizard-like workflow on a site, and I have a model for each one of the steps.
I have the following action methods:
public ActionResult Create();
public ActionResult Create01(Model01 m);
public ActionResult Create02(Model02 m);
public ActionResult Create03(Model03 m);
And I want the user to see the address as
/Element/Create
/Element/Create?Step=1
/Element/Create?Step=2
/Element/Create?Step=3
All the model classes inherit from a BaseModel that has a Step property.
The action methods that have the parameters have the correct AcceptVerbs constraint.
I tried naming all the methods Create, but that resulted in a AmbiguousMatchException.
What I want to do now is to create a custom route for each one of the actions, but I can't figure out how to do it.
This is what I tried:
routes.MapRoute(
"ElementsCreation",
"Element/Create",
new{controller="Element", action="Create01"},
new{Step="1"}
);
But this doesn't work.
Any help (on the correct MapRoute call or maybe a different approach) would be greatly appreciated.
Thanks
I actually found a different approach.
Instead of adding a new Route Map, I created a new Action Method attribute to verify if the passed request is valid for each of the action methods.
This is the attribute class:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ParameterValueMatchAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var value = controllerContext.RequestContext.HttpContext.Request[Name];
return (value == Value);
}
public string Value { get; set; }
public string Name { get; set; }
}
And I have each one of the action methods with the same name and decorated like this:
[AcceptVerbs(HttpVerbs.Post)]
[ParameterValueMatch(Name="Step", Value="1")]
public ActionResult Create(Model01 model)
I like this approach a LOT more than creating one route for each method.
Related
In a view, I am adding a custom input to the form. This input is not a part of the model that the view is bound to. It is highly customized and depends on the conditions at runtime.
When the controller handles the POST, I can access the posted form field with no problem.
[HttpPost]
[MyCustomActionFilter()]
public ActionResult Edit(int id, IJobType jobType, FormCollection formCollection)
{
// blah
string customValue = formCollection["CustomField"];
return View(jobType);
}
I have an ActionFilter that runs after the controller action to handle the custom input, but I see no easy way to access the FormCollection from this filter.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MyCustomActionFilter : FilterAttribute, IActionFilter
{
public MyCustomActionFilter()
{
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// How to access the FormCollection here?
}
}
Any ideas on how I can easily reference the FormCollection from this filter?
This is for ASP.NET MVC 5.
Thank you!
It seems you could do something like this
var formCollection = new FormCollection(
filterContext.HttpContext.Request.Form
);
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 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)
I keep running into scenarios where I would like to provide a slightly more intuitive or "well-formed" parameter name for action methods, but with the default behavior, this is turning out to be quite painful. For example, suppose that I have an action parameter like GetWidget(int id). If I want it to be GetWidget(int widgetId), I have to add a new route. It gets worse when you use a library like jqGrid which uses awful names for its querystring parameters: GetWidgets(int? nodeid, int? n_level). Instead, I'd like to have GetWidgets(int? parentId, int? level) or something similar.
So, is there something simple that I'm overlooking? It seems like it should be a very simple thing to tell MVC that my "parentId" parameter should be bound to the value of "nodeid" in the request. I thought about writing a custom action filter to do this, but it seems so obvious that I can't believe it's not supported out of the box.
As per Rony's answer use a custom model binder. Here is an example:
public class BindToAliasAttribute : CustomModelBinderAttribute
{
private readonly string parameterAlias;
public BindToAliasAttribute(string parameterAlias)
{
this.parameterAlias = parameterAlias;
}
public override IModelBinder GetBinder()
{
return new ParameterWithAliasModelBinder(parameterAlias);
}
}
public class ParameterWithAliasModelBinder : IModelBinder
{
private readonly string parameterAlias;
public ParameterWithAliasModelBinder(string parameterAlias)
{
this.parameterAlias = parameterAlias;
}
object IModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return controllerContext.RouteData.Values[parameterAlias];
}
}
public class UserController : Controller
{
[HttpGet]
public ActionResult Show( [BindToAlias("id")] string username)
{
...
}
}
If you use named parameters on the URL, you can specify a specific name for the parameter into your controller method, like so:
http://mydomain.com/mycontroller/getwidget?parentid=1&level=2
...and you won't have to match routes on the parameters.
use you own custom model binder which implements IModelBinder