Best way to pass multiple params from View to Controller via JQuery - asp.net-mvc

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)

Related

ASP.NET MVC Web API Models that are not tied to query string parameters

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());
...

ASP.NET MVC 3 : How to turn determine Controller Action from Url

Surprised I'm not finding this answer anywhere, how can I determine what Controller/Action will be invoked for a given URL in MVC 3?
Update
What I really want to know:
"how can I determine what ControllerAction will be invoked for a given URL in MVC 3?" ....yeah
So, either I'm not aware of the magic method that does this:
ControllerActionInfo GetControllerActionInfo(string url)
Or, I will have to create it myself doing whatever MVC does when it gets an http request.
My purpose of asking about this on StackOverflow is that I can save some time reverse engineering this behavior. The correct answer should resemble:
Here's how you can do it: and some code would follow.
You have to use a dummy HttpContext and HttpRequest classes as follows:
public class DummyHttpRequest : HttpRequestBase {
private string mUrl;
public DummyHttpRequest(string url) {
mUrl = url;
}
public override string AppRelativeCurrentExecutionFilePath {
get {
return mUrl;
}
}
public override string PathInfo {
get {
return string.Empty;
}
}
}
public class DummyHttpContext : HttpContextBase {
private string mUrl;
public DummyHttpContext(string url) {
mUrl = url;
}
public override HttpRequestBase Request {
get {
return new DummyHttpRequest(mUrl);
}
}
}
Edit: Also, you can extend the DefaultControllerFactory and add a simple method to get the desired information instead of an instance of Controller. (Note: It's merely a sample, you have to support other aspects like ActionNameAttribute and so on)
public class ControllerActionInfo {
public ControllerActionInfo(Type controllerType, MethodInfo action) {
ControllerType = controllerType;
Action = action;
}
public Type ControllerType { get; private set; }
public MethodInfo Action { get; private set; }
}
public class DefaultControllerFactoryEx : DefaultControllerFactory {
public ControllerActionInfo GetInfo(RequestContext requestContext, string controllerName) {
Type controllerType = GetControllerType(requestContext, controllerName);
if (controllerType == null) {
return null;
}
MethodInfo actionMethod = controllerType.GetMethod(requestContext.RouteData.GetRequiredString("action"), BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);
return new ControllerActionInfo(controllerType, actionMethod);
}
}
Then, use following code snippet to get access to the controller:
DummyHttpContext httpContext = new DummyHttpContext("~/home/index");
RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
// IController controller = new DefaultControllerFactory().CreateController(new RequestContext(httpContext, routeData), routeData.GetRequiredString("controller"));
DefaultControllerFactoryEx controllerFactory = new DefaultControllerFactoryEx();
var result = controllerFactory.GetInfo(new RequestContext(httpContext, routeData), routeData.GetRequiredString("controller"));
The logic for this is in the System.Web.Mvc.MvcHandler class, the System.Web.Mvc.DefaultControllerFactory class, and the System.Web.Mvc.ControllerActionInvoker class. .NET Reflector is your friend.
Basically, the MVC framework:
Uses reflection to get all the controllers in the application project.
Then it does something like IEnumerable<string> controllerNames = controllerTypes.Select(controllerType => controllerType.Name.Replace("Controller",string.Empty));. It then tries to match the first path segment, {controller}, to one of these sanitized controller type names (case-insensitive).
Then, it looks at this controller's public methods that have a return type that is of type ActionResult or some derivative. It matches the method name to the second path segment, {action}, as the action method to be called.
If the selected method has a parameter that is named id, then it matches the third path segment {id} to that value, and passes it to the method. Otherwise, the optional id parameter is ignored.
If the ActionResult type that is returned is a derivative of ViewResultBase then the IViewEngine tries to locate a corresponding view in the project using whatever conventions have been specified for that view engine. The WebFormViewEngine, for example, looks in the project for ~/Views/{controller}/{action}.ascx, ~/Views/{controller}/{action}.aspx, ~/Views/Shared/{action}.ascx, ~/Views/Shared/{action}.aspx by default.
If you want to further understand how routing works in MVC, I would highly suggest Scott Gu's article on MVC Routing.

How to redirect to an action with parameters from other action without passing parameters?

Below, in CreateTest, uponsuccessful, I want to redirect to Tests from CreateTest.
I want to do something like the following:
public ActionResult Tests(int ID, string projectName)
{
TestModel model = new TestModel (ID, projectName);
return View(model);
}
[HttpPost]
public ActionResult CreateTest(TestModel model)
{
try
{
return RedirectToAction("Tests");
}
catch (Exception e)
{
ModelState.AddModelError("Error", e.Message);
return View(model);
}
}
You might need to provide the arguments when redirecting:
return RedirectToAction("Tests", new {
ID = model.ID,
projectName = model.ProjectName
});
and the url you will be redirecting to will now look something like this:
/Foo/Tests?ID=123&projectName=abc
I know this is a bit old but...
What I've done in the past is have a "MessageArea" class exposed as a property on my base controller that all my controllers ultimately inherit from. The property actually stores the class instance in TempData. The MessageArea has a method to Add() which takes a string message and an enum Type (e.g. Success, Error, Warning, Information).
I then have a partial that renders whatever messages are in MessageArea with appropriate styling according to the type of the message.
I have a HTMLHelper extension method RenderMessageArea() so in any view I can simple say #Html.RenderMessageArea(), the method and partial take care of nulls and nothing is output if there are no messages.
Because data stored in TempData only survives 1 request it is ideal for cases where you want your action to redirect but have 1 or more messages shown on the destination page, e.g. an error, not authorised page etc... Or if you add an item but then return to the index list page.
Obviously you could implement something similar to pass other data. Ultimately I'd say this is a better solution to the original question than the accepted answer.
EDIT, EXAMPLE:
public class MessageAreaModel {
public MessageAreaModel() {
Messages = new List<Message>();
}
public List<Message> Messages { get; private set; }
public static void AddMessage(string text, MessageIcon icon, TempDatadictionary tempData) {
AddMessage(new Message(icon, text), tempData);
}
public static void AddMessage(Message message, TempDataDictionary tempData) {
var msgArea = GetAreaModelOrNew(tempData);
msgArea.Messages.Add(message);
tempData[TempDataKey] = msgArea;
}
private static MessageAreaModel GetAreaModelOrNew(TempDataDictionary tempData) {
return tempData[TempDataKey] as MessageAreaModel ?? new MessageAreaModel();
}
The above class can then be used to add messages from your UI layer used by the controllers.
Then add an HtmlHelper extension like so:
public static void RenderMessageArea(this HtmlHelper html) {
html.RenderPartial("MessageArea",
(MessageAreaModel)html.ViewContext.TempData[MessageAreaModel.TempDataKey] ?? MessageAreaModel.Empty);
html.ViewContext.TempData.Remove(MessageAreaModel.TempDataKey);
}
The above is not fully completed code there are various bells and whistles I've left out but you get the impression.
Make the int nullable:
public ActionResult Tests(int? ID, string projectName){
//...
}

ASP.NET MVC: Accessing public ActionFilter member from inside Action body

Let's say I've got an ActionFilterAttribute on an Action method in a Controller. This Action Filter exposes a couple public members (properties, in this case).
Is there any way from within the body of my Action method that I can access these public properties (read-only needed)?
Attribute properties could be only constant values and must be known at compile time:
[MyActionFilter(Prop1 = "SomeProp1", Prop2 = "SomeProp2")]
public ActionResult SomeAction()
{
// use "SomeProp1" and "SomeProp2" here
...
}
so inside the action you already know those values as you have hardcoded them just above the action method signature. To avoid hardcoding magic strings in two different places of you program you could use constants:
public const string Prop1 = "SomeProp1";
public const string Prop2 = "SomeProp2";
and then:
[MyActionFilter(Prop1 = Constants.Prop1, Prop2 = Constants.Prop2)]
public ActionResult SomeAction()
{
// use Constants.Prop1 and Constants.Prop2 here
...
}
Of course you could always use reflection:
var myFilters = (MyActionFilterAttribute[])MethodInfo.GetCurrentMethod()
.GetCustomAttributes(typeof(MyActionFilterAttribute), false);
if (myFilters.Length > 0)
{
var prop1 = myFilters[0].Prop1;
var prop2 = myFilters[0].Prop2;
}
but IMHO that would be a great waste so don't do it :-)

ASP.Net MVC: Map route according to parameter value

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.

Resources