Dynamic Form Building and Passing Query Parameters - asp.net-mvc-3

I am working on a form that is generated dynamically based on some meta-data tables in my database. I create input tags with names like setting_1, setting_53, setting_22 where the number is the primary key of the meta-data. Since the content is dynamic, I am using FormCollection as the sole parameter on POST requests.
Question 1: Is there a FormCollection-like class for GET requests? I want direct access to the query parameters.
Question 2: If I need to pass these query parameters around, is there an easy/safe way to build my URLs?
One of my big concerns is that some of the settings are populated via OAuth, so the user will be redirected to an external page. I will have to pass the query string as "state" which I will need to recover once the user returns. I will need to use this state to pick up where the user left off in the form entry process. All the more reason why I need a very fool-proof mechanism for passing query parameters around.
Has anyone dealt with dynamic pages like these? Are there good patterns and practices for passing these pages around?

Well, you can certainly look at Request.QueryString inside of a controller action.
But if it were me doing it, I'd write a custom model binder instead.
Here's a sample model binder. I haven't tested this!
public class MyModelBinder: DefaultModelBinder
{
private static void BindSettingProperty(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType != typeof(IDictionary<string, string>))
{
throw new InvalidOperationException("This binder is for setting dictionaries only.");
}
var originalValue = propertyDescriptor.GetValue(bindingContext.Model) as IDictionary<string, string>;
var value = originalValue ?? new Dictionary<string, string>();
var settingKeys = controllerContext.HttpContext.Request.QueryString.AllKeys.Where(k => k.StartsWith("setting_", StringComparison.OrdinalIgnoreCase));
foreach (var settingKey in settingKeys)
{
var key = settingKey.Substring(8);
value.Add(key, bindingContext.ValueProvider.GetValue(settingKey).AttemptedValue);
}
if (value.Any() && (originalValue == null))
{
propertyDescriptor.SetValue(bindingContext.Model, value);
}
}
protected override void BindProperty(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name.StartsWith("setting_", StringComparison.OrdinalIgnoreCase)
{
BindSettingProperty(controllerContext, bindingContext, propertyDescriptor);
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}

Related

Validate all strings

I want to prohibit the use of the | character for all strings submitted to my website but I don't want to have to apply a validator attribute to every string property because its unmanageable.
I could validate all strings in a model binder (I'm currently using one to trim all strings) but I don't think that would integrate with the standard validation framework. i.e. generating client side validation.
Any ideas how to do this?
I didn't get this working on the client however I can trap all dodgy strings in a custom model binder and make the form invalid with the following...
public class CustomStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue))
{
return null;
}
if (valueResult.AttemptedValue.Contains("|"))
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "The | character is prohibited.");
}
return valueResult.AttemptedValue.Trim();
}
}

Validating a view model after custom model binding

I have a view model that implements IValidatableObject that contains a string and a collection of another view model, something like this:
public sealed class MainViewModel
{
public string Name { get; set; }
public ICollection<OtherViewModel> Others { get; set; }
}
My validation checks each object in Others against different rules using the contract provided by IValidatableObject:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach (var other in this.Others)
{
// validate or yield return new ValidationResult
}
}
Because of the complex structure of the real MainViewModel I have had to create a custom model binder which re-builds the model and assigns POST data to the relevant components. The problem that I'm getting is that nothing is getting validated resulting in validation errors at the context level as it violates certain database constraints and I'm not sure what I'm doing wrong - I assumed that ModelState.IsValid would invoke the Validate method on my view model but it doesn't seem to go down that way.
My model binder looks like this:
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
int modelId = (int)controllerContext.RouteData.Values["id"];
// query the database and re-build the components of the view model
// iterate the POST data and assign to the model where necessary
// should I be calling something here to validate the model before it's passed to the controller?
return model;
}
Any help appreciated!
Validator.TryValidateObject
OK, seems I'm a little closer. I can now get my IValidatableObject method to run by adding the following to my custom model binder:
var validationResults = new HashSet<ValidationResult>();
var isValid = Validator.TryValidateObject(model, new ValidationContext(model, null, null), validationResults, true);
Seems that Validator.TryValidateObject invokes the validation method and setting the last parameter to true causes it to validate all properties. However, I'm now stuck with getting the validationResults to the controller so they can be used in a meaningful way.
I should have realised that I could use the ModelState.AddModelError through a custom binder, I've managed to get this working correctly now by adding the following to my custom model binder before returning the model to the controller:
var validationResults = new HashSet<ValidationResult>();
var isValid = Validator.TryValidateObject(model, new ValidationContext(model, null, null), validationResults, true);
if (!isValid)
{
foreach (var result in validationResults)
{
bindingContext.ModelState.AddModelError("", result.ErrorMessage);
}
}
return model;
This now returns a list of all errors to my page and the ModelState.IsValid check on my controller action is now returning false.
Paul's great answer can be refactored into a generic validate-and-convert to ModelState method as follows (e.g. in a helper or CustomModelBinder base). In addition, the bindings to the validated properties are retained.
public static void DoValidation(ModelBindingContext bindingContext,
IValidatableObject model)
{
var validationResults = new HashSet<ValidationResult>();
var isValid = Validator.TryValidateObject(model,
new ValidationContext(model, null, null), validationResults, true);
if (!isValid)
{
var resultsGroupedByMembers = validationResults
.SelectMany(_ => _.MemberNames.Select(
x => new {MemberName = x ?? "",
Error = _.ErrorMessage}))
.GroupBy(_ => _.MemberName);
foreach (var member in resultsGroupedByMembers)
{
bindingContext.ModelState.AddModelError(
member.Key,
string.Join(". ", member.Select(_ => _.Error)));
}
}
}

How do I get the MethodInfo of an action, given action, controller and area names?

What I have is the following extension method:
public MyCustomAttribute[] GetActionAttributes(
this Controller #this,
string action,
string controller,
string area,
string method)
{
}
How does ASP.NET MVC 3 find the action method, given the area, controller, action names and the method (GET, POST)?
To this moment I have nothing... no clues on how to do this.
I am currently looking for the stack trace inside a controller action, to find out how MVC dicovered it.
Why I need these attributes
My attributes contain information about whether a given user can or not access it... but depending on whether they can or not access it, I wan't to show or hide some html fields, links, and other things that could call that action.
Other uses
I have thought of using this to place an attribute over an action, that tells the css class of the link that will be rendered to call it... and some other UI hints... and then build an HtmlHelper that will render that link, looking at these attributes.
Not a duplicate
Yes, some will say this is possibly a duplicate of this question...
that does not have the answer I want:
How can i get the MethodInfo of the controller action that will get called given a request?
That's why I have specified the circumstances of my question.
I have looked inside MVC 3 source code, and tested with MVC 4, and discovered how to do it.
I have tagged the question wrong... it is not for MVC 3, I am using MVC 4. Though, as I could find a solution looking at MVC 3 code, then it may work with MVC 3 too.
At the end... I hope this is worth 5 hours of exploration, with a lot trials and errors.
Works with
MVC 3 (I think)
MVC 4 (tested)
Drawbacks of my solution
Unfortunately, this solution is quite complex, and dependent on things that I don't like very much:
static object ControllerBuilder.Current (very bad for unit testing)
a lot of classes from MVC (high coupling is always bad)
not universal (it works with MVC 3 default objects, but may not work with other implementations derived from MVC... e.g. derived MvcHandler, custom IControllerFactory, and so on ...)
internals dependency (depends on specific aspects of MVC 3, (MVC 4 behaves like this too) may be MVC 5 is different... e.g. I know that RouteData object is not used to find the controller type, so I simply use stub RouteData objects)
mocks of complex objects to pass data (I needed to mock HttpContextWrapper and HttpRequestWrapper in order to set the http method to be POST or GET... these pretty simple values comes from complex objects (oh god! =\ ))
The code
public static Attribute[] GetAttributes(
this Controller #this,
string action = null,
string controller = null,
string method = "GET")
{
var actionName = action
?? #this.RouteData.GetRequiredString("action");
var controllerName = controller
?? #this.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current
.GetControllerFactory();
var controllerContext = #this.ControllerContext;
var otherController = (ControllerBase)controllerFactory
.CreateController(
new RequestContext(controllerContext.HttpContext, new RouteData()),
controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(
otherController.GetType());
var controllerContext2 = new ControllerContext(
new MockHttpContextWrapper(
controllerContext.HttpContext.ApplicationInstance.Context,
method),
new RouteData(),
otherController);
var actionDescriptor = controllerDescriptor
.FindAction(controllerContext2, actionName);
var attributes = actionDescriptor.GetCustomAttributes(true)
.Cast<Attribute>()
.ToArray();
return attributes;
}
EDIT
Forgot the mocked classes
class MockHttpContextWrapper : HttpContextWrapper
{
public MockHttpContextWrapper(HttpContext httpContext, string method)
: base(httpContext)
{
this.request = new MockHttpRequestWrapper(httpContext.Request, method);
}
private readonly HttpRequestBase request;
public override HttpRequestBase Request
{
get { return request; }
}
class MockHttpRequestWrapper : HttpRequestWrapper
{
public MockHttpRequestWrapper(HttpRequest httpRequest, string httpMethod)
: base(httpRequest)
{
this.httpMethod = httpMethod;
}
private readonly string httpMethod;
public override string HttpMethod
{
get { return httpMethod; }
}
}
}
Hope all of this helps someone...
Happy coding for everybody!
You can achieve this functionality by using the AuthorizeAttribute. You can get the Controller and Action name in OnAuthorization method. PLease find sample code below.
public sealed class AuthorizationFilterAttribute : AuthorizeAttribute
{
/// <summary>
/// Use for validate user permission and when it also validate user session is active.
/// </summary>
/// <param name="filterContext">Filter Context.</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
string actionName = filterContext.ActionDescriptor.ActionName;
string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
if (!IsUserHasPermission(controller, actionName))
{
// Do your required opeation
}
}
}
if you have a default route configured like
routes.MapRoute(
"Area",
"",
new { area = "MyArea", controller = "Home", action = "MyAction" }
);
you can get the route information inside the controller action like
ht tp://localhost/Admin
will give you
public ActionResult MyAction(string area, string controller, string action)
{
//area=Admin
//controller=Home
//action=MyAction
//also you can use RouteValues to get the route information
}
here is a great blog post and a utility by Phil Haack RouteDebugger 2.0
This is a short notice! Be sure to use filterContext.RouteData.DataTokens["area"]; instead of filterContext.RouteData.Values["area"];
Good Luck.

MVC3 Return View By Default

Basically, I was wondering if anyone knows of a way that you can set up MVC3 in a way that it will first look for an action, and if none exists, it will automatically return the view at that location. Otherwise each time I make a page, I will have to rebuild it after adding the action.
It isn't something that's stopping the project from working nor is it an issue, it would just be a very nice thing to include in the code to help with speed of testing more than anything.
EDIT:
Just for clarity purposes, this is what I do every time I create a view that doesn't have any logic inside it:
public ActionResult ActionX()
{
return View();
}
Sometimes I will want some logic inside the action, but majority of the time for blank pages I will just want the above code.
I would like it if there was any way to always return the above code for every Controller/Action combination, UNLESS I have already made an action, then it should use the Action that I have specified.
Thanks,
Jake
Why not just create a single action for this. This will look for a view with the specified name and return a 404 if it doesn't exist.
[HttpGet]
public ActionResult Page(string page)
{
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, page, null);
if (result == null)
{
return HttpNotFound();
}
return View(page);
}
Then make your default route fall back to this:
routes.MapRoute("", "{page}", new { controller = "Home", action = "Page" });
So a request to http://yoursite.com/somepage will invoke Page("somepage")
I'm not altogether sure how useful this will be (or whether its really a good idea) but I guess if you have pages which are purely static content (but maybe use a layout or something so you can't use static html) it could be useful
This is how it could be done though anyway (as a base class, but it doesn't have to be)
public abstract class BaseController : Controller
{
public ActionResult Default()
{
return View();
}
protected override IActionInvoker CreateActionInvoker()
{
return new DefaultActionInvoker();
}
private class DefaultActionInvoker : ControllerActionInvoker
{
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
var actionDescriptor = base.FindAction(controllerContext, controllerDescriptor, actionName);
if (actionDescriptor == null)
actionDescriptor = base.FindAction(controllerContext, controllerDescriptor, "Default");
return actionDescriptor;
}
}
}

MVC Model Binding a Complex Type to a Simple Type and Vice Versa

Here's a scenario:
I have an autocomplete plugin (custom) that keeps a hidden field of JSON objects (using a specific struct).
I've created an Html helper that helps me easily bind to a specific custom model (basically, it has a JSON property that is for two-way binding and a property that lets me deserialize the JSON into the appropriate struct):
public class AutoCompleteModel {
public string JSON { get; set; }
public IEnumerable<Person> People {
get {
return new JavaScriptSerializer().Deserialize<Person>(this.JSON);
}
set {
this.JSON = new JavaScriptSerializer().Serialize(value);
}
}
}
This works great and I can model bind using the default binder #Html.Autocomplete(viewModel => viewModel.AutoCompleteModelTest). The HTML helper generates HTML like:
<input type="text" id="AutoCompleteModelTest_ac" name="AutoCompleteModelTest_ac" value="" />
<input type="hidden" id="AutoCompleteModelTest_JSON" name="AutoCompleteModelTest.JSON" value="{JSON}" />
The problem is this is not the best way for consumers. They have to manually set the People property to an array of Person structs. In my data layer, my domain objects probably will not be storing the full struct, only the person's ID (a corporate ID). The autocomplete will take care of looking up the person itself if only given an ID.
The best scenario will be to call it like this:
#Html.Autocomplete(domainObject => domainObject.PersonID) or
#Html.Autocomplete(domainObject => domainObject.ListOfPersonIDs
I would like it to work against the string property AND against the custom AutoCompleteModel. The autocompleter only updates a single hidden field, and that field name is passed back on postback (the value looks like: [{ "Id":"12345", "FullName":"A Name"},{ "Id":"12347", "FullName":"Another Name" }]).
The problem is, of course, that those domain object properties only have an ID or array of IDs, not a full Person struct (so cannot be directly serialized into JSON). In the HTML helper, I can transform those property values into a struct, but I don't know how to transform it back into a simple type on POST. The solution I need would transform an ID into a new Person struct on page load, serializing it into the hidden field. On POST, it would deserialize the generated JSON back into a simple array of IDs.
Is a custom model binder the solution I need? How can I tell it to work both with a custom model AND simple types (because I don't want it applied to EVERY string property, just need it to deal with the values given by the HTML helper).
I figured it out, it's possible!
To clarify, I needed to: transform a string or string array (of IDs) into a JSON structure for my hidden field value, then on post back, deserialize the JSON in the hidden field and transform the struct back into a simple string or string array (of IDs) for my domain object's property.
Step 1: Create a HTML helper
I had done this already, but only for accepting my custom AutoCompleteModel type. I needed one for a string and an Enumerable of string type.
All I did was generate my Person struct(s) from the value of the property and serialize them into JSON for the hidden field the Autocompleter uses (this is an example of the string helper, I also have a nearly identical one for IEnumerable<string>):
public static MvcHtmlString AutoComplete<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, string>> idProp)
where TModel : class
{
TModel model = htmlHelper.ViewData.Model;
string id = idProp.Compile().Invoke(model);
string propertyName = idProp.GetPropertyName();
Person[] people = new Person[] {
new Person() { ID = id }
};
// Don't name the textbox the same name as the property,
// otherwise the value will be whatever the textbox is,
// if you care.
MvcHtmlString textBox = htmlHelper.TextBox(propertyName + "_ac", string.Empty);
// For me, the JSON is the value I want to postback
MvcHtmlString hidden = htmlHelper.Hidden(propertyName, new JavaScriptSerializer().Serialize(people));
return MvcHtmlString.Create(
"<span class=\"AutoComplete\">" +
textBox.ToHtmlString() +
hidden.ToHtmlString() +
"</span>");
}
Usage: #Html.AutoComplete(model => model.ID)
Step 2: Create a custom model binder
The crux of my issue was that I needed this binder to only apply to certain properties, and they were strings or string arrays.
I was inspired by this article because it used Generics. I decided, hey, we can just ask people what property they want to apply the binder for.
public class AutoCompleteBinder<T> : DefaultModelBinder
where T : class
{
private IEnumerable<string> PropertyNames { get; set; }
public AutoCompleteBinder(params Expression<Func<T, object>>[] idProperties)
{
this.PropertyNames = idProperties.Select(x => x.GetPropertyName());
}
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
var submittedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (submittedValue != null && this.PropertyNames.Contains(propertyDescriptor.Name))
{
string json = submittedValue.AttemptedValue;
Person[] people = new JavaScriptSerializer().Deserialize<Person[]>(json);
if (people != null && people.Any())
{
string[] IDs = people.Where(x => !string.IsNullOrEmpty(x.ID)).Select(x => x.ID).ToArray();
bool isArray = bindingContext.ModelType != typeof(string) &&
(bindingContext.ModelType == typeof(string[]) ||
bindingContext.ModelType.HasInterface<IEnumerable>());
if (IDs.Count() == 1 && !isArray)
return IDs.First(); // return string
else if (IDs.Count() > 0 && isArray)
return IDs.ToArray(); // return string[]
else
return null;
}
else
{
return null;
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
GetPropertyName() (translate LINQ expression into a string, i.e. m => m.ID = ID) and HasInterface() are just two utility methods I have.
Step 3: Register
Register the binder on your domain objects and their properties in Application_Start:
ModelBinders.Binders.Add(typeof(Employee), new AutoCompleteBinder<Employee>(e => e.ID, e => e.TeamIDs));
It's only a little bit annoying to have to register the binder for specific properties, but it's not the end of the world and provides a nice, smooth experience working with my autocompleter.
Any comments are welcome.

Resources