Alternate names for querystring parameters - c#

Is it somehow possible to set alternate names for querystring parameters in ASP.NET MVC?
I have this simple controller Index action:
public ActionResult Index(color = "")
{
...
}
Calling http://mysite.com/mypage/?color=yellow works quite nicely, the color parameter automatically picks up its value "yellow" from the querystring.
But now I would like to have a localized variant of the same page, with “pretty” localized parameters, but still working with the same controller method. Example: http://mysite.com/mypage/?farve=gul. Here I would like “gul” to be passed in as the color parameter to the default Index() ation method.
How do I set mappings for alternate names for querystring parameters?

How do I set mappings for alternate names for querystring parameters?
You could write a custom model binder.
So as in every ASP.NET MVC application you start by writing a view model:
public class MyViewModel
{
public string Color { get; set; }
}
and then a model binder for this model:
public class MyViewModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var query = controllerContext.HttpContext.Request.QueryString;
var value = query["color"] ?? query["gul"] ?? query["couleur"];
return new MyViewModel
{
Color = value,
};
}
}
which will be registered at your Application_Start:
ModelBinders.Binders.Add(typeof(MyViewModel), new MyViewModelBinder());
and now your controller action may take the view model as parameter:
public ActionResult Index(MyViewModel model)
{
...
}
Of course you could make the model binder more flexible by using some custom attribute on the property:
public class MyViewModel
{
[PossibleQueries("color", "gul", "couleur")]
public string Color { get; set; }
}
and in the model binder read those values and try reading them from the query string until you find one that is not null.

How about a second controller with a localized/pretty name where the actions and parameters have localized names and call the actions from the default/english controller? With this method you have all parts of the url localized.
Controller mypage
{
ActionResult Index(string color)
{
// normal code
}
}
Controller meineseite
{
ActionResult Index(string farbe)
{
return mypage.Index(farbe);
}
}

Related

ASP.NET MVC Attribute routing parameter issue

I have the following code
public class BooksController : Controller
{
[Route("/Books/{id?}")]
public IActionResult Index(string id)
{
return View(id);
}
}
My problem is that when I try to enter the parameter it is (as it seems) considered as controller's action so I keep getting this exception.
I need somebody to explain what am I doing wrong.
If you want to pass some parameter to a view as a string you can make this like below:
public class BooksController : Controller
{
[Route("/Books/{id?}")]
public IActionResult Index(string id)
{
if (string.IsNullOrEmpty(id))
id = "default_value";
return View((object)id);
}
}
If the string type is passing to the View() call without casting to object it will be interpreted as a view name.
And the view model data should be declared as
#model string;
<h2>#Model</h2>
Try changing the route as given below -
[Route("Books", Name = "id")]

ActionFilter to change posted values fails to impact the model

We are trying to sanitize posted string entries in an ASP.NET MVC web application in a "global" way. With my current attempt, I wrote a custom action filter and decorated a post action with my class FormPostSanitizer. The idea being that we'll decorate all post actions that should be sanitized.
The method successfully captures and sanitizes the inputs. But those sanitized values aren't being fixed to the model before it gets saved to the database.
Here is how it decorates the controller.
[HttpPost]
[ValidateAntiForgeryToken]
[FormPostSanitizer]
public ActionResult MyAction(MyViewModel model)
{
// If ModelState.IsValid, save the model ...
}
Here is my action filter.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.HttpMethod != "POST") return;
FormCollection formCollection = new FormCollection(filterContext.Controller.ControllerContext.HttpContext.Request.Form);
foreach (string key in formCollection.AllKeys)
{
string sanitized = formCollection[key].SanitizeString(); // Extension method to alter the string.
formCollection.Set(key, sanitized);
}
filterContext.ActionParameters["form"] = formCollection;
}
My expectation was the last line would commit the changed values to the filterContext, and the model would have the sanitized values. The values get sanitized, but they aren't getting applied to the model.
If there is a better way in the compiled code to intercept and alter the posted values before they are bound to the model, then please point me to a post that shows the methodology. Thanks for your help.
You can create custom model binder for this purpose
public class SanitizeModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
bool sanitize = controllerContext.HttpContext.Request.HttpMethod == "POST";
//get value from default binder
object value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
if (!sanitize)
{
return value;
}
//sanitize value if it is a string
string stringValue = value as string;
if (stringValue != null)
{
return stringValue.SanitizeString();
}
return value;
}
}
Set default binder in Global.asax.cs to use it for every action
System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new SanitizeModelBinder();
or if you want to use this binder for certain models
public ActionResult MyAction([ModelBinder(typeof(SanitizeModelBinder))]MyViewModel model)
You have to use a custom model binder like Alexandar explained above as the action filter attribute is executed after the model is bound already and you cannot change it at this point.
If you do it in the Model Binder then it will be applied to the Model as you would expect

Make a complex typed property required in an MVC4 form

I can't figure out how to "customize" the rules for the [Required] attribute when I stick it to a custom typed property. Code looks like this:
public class MyProp
{
public Guid Id {get;set;}
public string Target {get;set;}
}
public class MyType : IValidatableObject
{
public string Name {get;set;}
public MyProp Value {get;set;}
private MyType()
{
this.Name = string.Empty;
this.Value = new MyProp { Id = Guid.Empty, Target = string.Empty };
}
public MyType(Guid id) : this()
{
this.Value.Id = id;
// Fill rest of data through magic
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(this.Value.Id == Guid.Empty)
yield return new ValidationResult("You must fill the property");
}
}
This model shows up in forms (through its own EditorTemplate) as a textbox with a button which allows for selection from a list (the backing data is a Dynamics CRM 2011 Environment, and this model is actually aimed to represent a lookup attribute).
public class MyModel
{
// Many props
[Required] // This one is enforced correctly
public string MyString {get;set;}
[Required] // This one isn't
public MyType MyData {get;set;}
public MyModel() { this.MyData = new MyType(); }
}
The resulting view shows the field (empty, of course). User can only input data by clicking the field and choosing from a list (a jquery dialog takes care of this, and it already works).
The IValidatableObject interface sounds promising but the code doesn't seem to be ever invoked.
In the controller, I'm simply doing
[HttpPost]
public ActionResult MyAction(FormCollection data)
{
if (!ModelState.IsValid) return View();
// magic: handle data
}
What am I missing ? I probably misunderstood the IValidatableObject interface usage ?
Your controller action should take the view model as parameter instead of weakly typed FormCollection which has absolutely no relation to your model (and its validation rules):
[HttpPost]
public ActionResult MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
return View();
}
// magic: handle model
}
Now the default model binder is going to be invoked in order to bind the view model from the request and evaluate any validation logic you might have in this model.
How do you expect from your code, ASP.NET MVC, to ever know that you are working with this MyModel class? You absolutely never used it in your POST action, so you cannot expect to have any validation on it.
Once you start using view models you should forget about weakly typed collections such as FormCollection and start working with those view models.

Map post parameters to a model

I have a model I want to use for communication with an external web service. It's supposed to call a specific post action on my website.
public class ConfirmationModel{
...
public string TransactionNumber {get; set;}
}
public ActionResult Confirmation(ConfirmationModel){
...
}
The problem is the parameters names they pass are not very human-readable. And I want to map them to my more readable model.
't_numb' ====> 'TransactionNumber'
Can this be done automatically? With an attribute maybe? What's the best approach here?
Create a model binder:
using System.Web.Mvc;
using ModelBinder.Controllers;
public class ConfirmationModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = new ConfirmationModel();
var transactionNumberParam = bindingContext.ValueProvider.GetValue("t_numb");
if (transactionNumberParam != null)
model.TransactionNumber = transactionNumberParam.AttemptedValue;
return model;
}
}
Initialise it in Global.asax.cs:
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof(ConfirmationModel), new ConfirmationModelBinder());
}
Then in your action method
[HttpPost]
public ActionResult Confirmation(ConfirmationModel viewModel)
You should see the value of t_numb appear in TransactionNumber property of the viewmodel.
Agree that model binder is better: here's an alternate idea though
public ActionResult Create(FormCollection values)
{
Recipe recipe = new Recipe();
recipe.Name = values["Name"];
// ...
return View();
}
and a good read about both: http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx

How to get attributes of parameter I am trying to bind to in IModelBinder?

Is there a way to access the attributes of the controller action parameter currently being processed from within IModelBinder.BindModel()?
In particular, I am writing a binder for binding request data to an arbitrary Enum type (specified as a template parameter to the model binder) and I would like to specify for each controller action parameter for which I want to use this binder a name of the HTTP request value to get the Enum values from.
Example:
public ViewResult ListProjects([ParseFrom("jobListFilter")] JobListFilter filter)
{
...
}
and the model binder:
public class EnumBinder<T> : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
// Get the ParseFrom attribute of the action method parameter
// From the attribute, get the FORM field name to be parsed
//
string formField = GetFormFieldNameToBeParsed();
return ConvertToEnum<T>(ReadValue(formField));
}
}
I suspect there may possibly be another, more appropriate, point in the request workflow where I would supply the attribute value.
Found out how to do it using CustomModelBinderAttribute-derived class:
public class EnumModelBinderAttribute : CustomModelBinderAttribute
{
public string Source { get; set; }
public Type EnumType { get; set; }
public override IModelBinder GetBinder()
{
Type genericBinderType = typeof(EnumBinder<>);
Type binderType = genericBinderType.MakeGenericType(EnumType);
return (IModelBinder) Activator.CreateInstance(binderType, this.Source);
}
}
Now the action method looks like this:
public ViewResult ListProjects([EnumModelBinder(EnumType=typeof(JobListFilter), Source="formFieldName")] JobListFilter filter)
{
...
}

Categories