Serialize IList property on model when passed into Html.ActionLink - asp.net-mvc

I'm trying to generate an Html.ActionLink with the following viewmodel:
public class SearchModel
{
public string KeyWords {get;set;}
public IList<string> Categories {get;set;}
}
To generate my link I use the following call:
#Html.ActionLink("Index", "Search", Model)
Where Model is an instance of the SearchModel
The link generated is something like this:
http://www.test.com/search/index?keywords=bla&categories=System.Collections.Generic.List
Because it obviously is only calling the ToString method on every property.
What I would like to see generate is this:
http://www.test.com/search/index?keywords=bla&categories=Cat1&categories=Cat2
Is there any way I can achieve this by using Html.ActionLink

In MVC 3 you're just out of luck because the route values are stored in a RouteValueDictionary that as the name implies uses a Dictionary internally which makes it not possible to have multiple values associated to a single key. The route values should probably be stored in a NameValueCollection to support the same behavior as the query string.
However, if you can impose some constraints on the categories names and you're able to support a query string in the format:
http://www.test.com/search/index?keywords=bla&categories=Cat1|Cat2
then you could theoretically plug it into Html.ActionLink since MVC uses TypeDescriptor which in turn is extensible at runtime. The following code is presented to demonstrate it's possible, but I would not recommend it to be used, at least without further refactoring.
Having said that, you would need to start by associating a custom type description provider:
[TypeDescriptionProvider(typeof(SearchModelTypeDescriptionProvider))]
public class SearchModel
{
public string KeyWords { get; set; }
public IList<string> Categories { get; set; }
}
The implementation for the provider and the custom descriptor that overrides the property descriptor for the Categories property:
class SearchModelTypeDescriptionProvider : TypeDescriptionProvider
{
public override ICustomTypeDescriptor GetTypeDescriptor(
Type objectType, object instance)
{
var searchModel = instance as SearchModel;
if (searchModel != null)
{
var properties = new List<PropertyDescriptor>();
properties.Add(TypeDescriptor.CreateProperty(
objectType, "KeyWords", typeof(string)));
properties.Add(new ListPropertyDescriptor("Categories"));
return new SearchModelTypeDescriptor(properties.ToArray());
}
return base.GetTypeDescriptor(objectType, instance);
}
}
class SearchModelTypeDescriptor : CustomTypeDescriptor
{
public SearchModelTypeDescriptor(PropertyDescriptor[] properties)
{
this.Properties = properties;
}
public PropertyDescriptor[] Properties { get; set; }
public override PropertyDescriptorCollection GetProperties()
{
return new PropertyDescriptorCollection(this.Properties);
}
}
Then we would need the custom property descriptor to be able to return a custom value in GetValue which is called internally by MVC:
class ListPropertyDescriptor : PropertyDescriptor
{
public ListPropertyDescriptor(string name)
: base(name, new Attribute[] { }) { }
public override bool CanResetValue(object component)
{
return false;
}
public override Type ComponentType
{
get { throw new NotImplementedException(); }
}
public override object GetValue(object component)
{
var property = component.GetType().GetProperty(this.Name);
var list = (IList<string>)property.GetValue(component, null);
return string.Join("|", list);
}
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType
{
get { throw new NotImplementedException(); }
}
public override void ResetValue(object component) { }
public override void SetValue(object component, object value) { }
public override bool ShouldSerializeValue(object component)
{
throw new NotImplementedException();
}
}
And finally to prove that it works a sample application that mimics the MVC route values creation:
static void Main(string[] args)
{
var model = new SearchModel { KeyWords = "overengineering" };
model.Categories = new List<string> { "1", "2", "3" };
var properties = TypeDescriptor.GetProperties(model);
var dictionary = new Dictionary<string, object>();
foreach (PropertyDescriptor p in properties)
{
dictionary.Add(p.Name, p.GetValue(model));
}
// Prints: KeyWords, Categories
Console.WriteLine(string.Join(", ", dictionary.Keys));
// Prints: overengineering, 1|2|3
Console.WriteLine(string.Join(", ", dictionary.Values));
}
Damn, this is probably the longest answer I ever give here at SO.

with linq of course...
string.Join("", Model.Categories.Select(c=>"&categories="+c))

Related

How to modify binded value in MVVM Cross

I have a model from my json in a Xamarin MVVM app(ios). I want to add the "%" after the value? in the list "coinmarketcaplist" contains the value 24h_change, this is the value I want to add a % to, it's a string. I know that I should use a getter for it, but I don't know how since I'm fairly new to this. below is my ViewModel code:
public class CMCTableViewModel : MvxViewModel
{
protected readonly ICoinMarketCapService _coinMarketCapService;
public CMCTableViewModel(ICoinMarketCapService coinMarketCapService)
{
_coinMarketCapService = coinMarketCapService;
LoadData();
}
private List<CoinMarketCapModel> _coinMarketCapModelList;
public List<CoinMarketCapModel> CoinMarketCapModelList
{
get
{
return _coinMarketCapModelList;
}
set
{
_coinMarketCapModelList = value;
RaisePropertyChanged(() => CoinMarketCapModelList);
}
}
public async void LoadData()
{
CoinMarketCapModelList = await _coinMarketCapService.GetCoins();
}
}
TableCell:
internal static readonly NSString Identifier = new NSString("CMCTableCell");
public override void LayoutSubviews()
{
base.LayoutSubviews();
MvxFluentBindingDescriptionSet<CMCTableCell, CoinMarketCapModel> set = new MvxFluentBindingDescriptionSet<CMCTableCell, CoinMarketCapModel>(this);
set.Bind(lblName).To(res => res.Name);
set.Bind(lblPrice).To(res => res.percent_change_24h);
set.Bind(imgCoin)
.For(img => img.Image)
.To(res => res.image)
.WithConversion<StringToImageConverter>();
set.Apply();
}
}
edit: added cellview
Use a converter in your binding:
1) Define converter:
public class StringFormatValueConverter : MvxValueConverter
{
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return null;
if (parameter == null)
return value;
return string.Format(parameter.ToString(), value);
}
}
2) Use it in your binding:
set.Bind(lblPrice).To(res => res.percent_change_24h).WithConversion<StringFormatValueConverter>("{0} %");
You can use this converter when you want to modify the input string by adding something around it, for example unit or currency

What is the best way to prohibit integer value for Enum action's parameter

I use some Enum as parameter in some action. For example we have the following code
public enum SomeEnum { SomeVal1 = 1, SomeVal2 = 2 }
[HttpGet]
public void SomeAction(SomeEnum someParameter) { }
By default asp.net engine allows to use both string and integer values that's why we are able to call it like this 'http://host/SomeController/SomeAction/SomeVal1' or this 'http://host/SomeController/SomeAction/1' or even this 'http://host/SomeController/SomeAction/54'! I would like to stay with the first sample using string value. For this I've implemented the following model binder:
public class RequireStringsAttribute : ModelBinderAttribute
{
public RequireStringsAttribute() : base(typeof(ModelBinder))
{
}
private class ModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.FieldName).FirstValue;
var isValid = Enum.GetNames(bindingContext.ModelType).Any(name =>
name.Equals(value, StringComparison.OrdinalIgnoreCase));
if (isValid)
{
bindingContext.Result = ModelBindingResult.Success(Enum.Parse(bindingContext.ModelType, value, ignoreCase: true));
}
else
{
bindingContext.ActionContext.ModelState.AddModelError(bindingContext.FieldName, $"The value '{value}' is not valid.");
}
return Task.CompletedTask;
}
}
}
And I've applied it:
[HttpGet]
public void SomeAction([RequireStrings]SomeEnum someParameter) { }
It works fine but I just want to know is there a better way to do it?

Get custom attribute for parameter when model binding

I've seen a lot of similar posts on this, but haven't found the answer specific to controller parameters.
I've written a custom attribute called AliasAttribute that allows me to define aliases for parameters during model binding. So for example if I have: public JsonResult EmailCheck(string email) on the server and I want the email parameter to be bound to fields named PrimaryEmail or SomeCrazyEmail I can "map" this using the aliasattribute like this: public JsonResult EmailCheck([Alias(Suffix = "Email")]string email).
The problem: In my custom model binder I can't get a hold of the AliasAttribute class applied to the email parameter. It always returns null.
I've seen what the DefaultModelBinder class is doing to get the BindAttribute in reflector and its the same but doesn't work for me.
Question: How do I get this attribute during binding?
AliasModelBinder:
public class AliasModelBinder : DefaultModelBinder
{
public static ICustomTypeDescriptor GetTypeDescriptor(Type type)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var descriptor = GetTypeDescriptor(bindingContext.ModelType);
/*************************/
// this next statement returns null!
/*************************/
AliasAttribute attr = (AliasAttribute)descriptor.GetAttributes()[typeof(AliasAttribute)];
if (attr == null)
return null;
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var key in request.Form.AllKeys)
{
if (string.IsNullOrEmpty(attr.Prefix) == false)
{
if (key.StartsWith(attr.Prefix, StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
return request.Form.Get(key);
}
}
else if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
if (attr.HasIncludes)
{
foreach (var include in attr.InlcludeSplit)
{
if (key.Equals(include, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(include);
}
}
}
}
return null;
}
}
AliasAttribute:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class AliasAttribute : Attribute
{
private string _include;
private string[] _inlcludeSplit = new string[0];
public string Prefix { get; set; }
public string Suffix { get; set; }
public string Include
{
get
{
return _include;
}
set
{
_include = value;
_inlcludeSplit = SplitString(_include);
}
}
public string[] InlcludeSplit
{
get
{
return _inlcludeSplit;
}
}
public bool HasIncludes { get { return InlcludeSplit.Length > 0; } }
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
}
Usage:
public JsonResult EmailCheck([ModelBinder(typeof(AliasModelBinder)), Alias(Suffix = "Email")]string email)
{
// email will be assigned to any field suffixed with "Email". e.g. PrimaryEmail, SecondaryEmail and so on
}
Gave up on this and then stumbled across the Action Parameter Alias code base that will probably allow me to do this. It's not as flexible as what I started out to write but probably can be modified to allow wild cards.
what I did was make my attribute subclass System.Web.Mvc.CustomModelBinderAttribute which then allows you to return a version of your custom model binder modified with the aliases.
example:
public class AliasAttribute : System.Web.Mvc.CustomModelBinderAttribute
{
public AliasAttribute()
{
}
public AliasAttribute( string alias )
{
Alias = alias;
}
public string Alias { get; set; }
public override IModelBinder GetBinder()
{
var binder = new AliasModelBinder();
if ( !string.IsNullOrEmpty( Alias ) )
binder.Alias = Alias;
return binder;
}
}
which then allows this usage:
public ActionResult Edit( [Alias( "somethingElse" )] string email )
{
// ...
}

attribute dependent on another field

In a model of my ASP.NET MVC application I would like validate a textbox as required only if a specific checkbox is checked.
Something like
public bool retired {get, set};
[RequiredIf("retired",true)]
public string retirementAge {get, set};
How can I do that?
Thank you.
Take a look at this: http://blogs.msdn.com/b/simonince/archive/2010/06/04/conditional-validation-in-mvc.aspx
I've modded the code somewhat to suit my needs. Perhaps you benefit from those changes as well.
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentUpon { get; set; }
public object Value { get; set; }
public RequiredIfAttribute(string dependentUpon, object value)
{
this.DependentUpon = dependentUpon;
this.Value = value;
}
public RequiredIfAttribute(string dependentUpon)
{
this.DependentUpon = dependentUpon;
this.Value = null;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{ }
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
// no client validation - I might well blog about this soon!
return base.GetClientValidationRules();
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentUpon);
if (field != null)
{
// get the value of the dependent property
var value = field.GetValue(container, null);
// compare the value against the target value
if ((value != null && Attribute.Value == null) || (value != null && value.Equals(Attribute.Value)))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
Then use it:
public DateTime? DeptDateTime { get; set; }
[RequiredIf("DeptDateTime")]
public string DeptAirline { get; set; }
Just use the Foolproof validation library that is available on Codeplex:
https://foolproof.codeplex.com/
It supports, amongst others, the following "requiredif" validation attributes / decorations:
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
To get started is easy:
Download the package from the provided link
Add a reference to the included .dll file
Import the included javascript files
Ensure that your views references the included javascript files from within its HTML for unobtrusive javascript and jquery validation.
Using NuGet Package Manager I intstalled this: https://github.com/jwaliszko/ExpressiveAnnotations
And this is my Model:
using ExpressiveAnnotations.Attributes;
public bool HasReferenceToNotIncludedFile { get; set; }
[RequiredIf("HasReferenceToNotIncludedFile == true", ErrorMessage = "RelevantAuditOpinionNumbers are required.")]
public string RelevantAuditOpinionNumbers { get; set; }
I guarantee you this will work!
I have not seen anything out of the box that would allow you to do this.
I've created a class for you to use, it's a bit rough and definitely not flexible.. but I think it may solve your current problem. Or at least put you on the right track.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
namespace System.ComponentModel.DataAnnotations
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' is required";
private readonly object _typeId = new object();
private string _requiredProperty;
private string _targetProperty;
private bool _targetPropertyCondition;
public RequiredIfAttribute(string requiredProperty, string targetProperty, bool targetPropertyCondition)
: base(_defaultErrorMessage)
{
this._requiredProperty = requiredProperty;
this._targetProperty = targetProperty;
this._targetPropertyCondition = targetPropertyCondition;
}
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, _requiredProperty, _targetProperty, _targetPropertyCondition);
}
public override bool IsValid(object value)
{
bool result = false;
bool propertyRequired = false; // Flag to check if the required property is required.
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
string requiredPropertyValue = (string) properties.Find(_requiredProperty, true).GetValue(value);
bool targetPropertyValue = (bool) properties.Find(_targetProperty, true).GetValue(value);
if (targetPropertyValue == _targetPropertyCondition)
{
propertyRequired = true;
}
if (propertyRequired)
{
//check the required property value is not null
if (requiredPropertyValue != null)
{
result = true;
}
}
else
{
//property is not required
result = true;
}
return result;
}
}
}
Above your Model class, you should just need to add:
[RequiredIf("retirementAge", "retired", true)]
public class MyModel
In your View
<%= Html.ValidationSummary() %>
Should show the error message whenever the retired property is true and the required property is empty.
Hope this helps.
Try my custom validation attribute:
[ConditionalRequired("retired==true")]
public string retirementAge {get, set};
It supports multiple conditions.

What's wrong with my AutoMapper Customer ValueResolver Hook?

I have the following hook in my Global.aspx
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
AutoMapper.Mapper.CreateMap<FormCollection, Models.IAmACustomer>().ForAllMembers(form => form.ResolveUsing<Models.FormCollectionValueResolver<Models.IAmACustomer>>());
}
In My controller:
[HttpPost]
public ActionResult Create(FormCollection formCollection)
{
var customer = AutoMapper.Mapper.Map<FormCollection,Models.IAmACustomer> (formCollection,null);
}
This line executes but my custom resolver is never called.
The resolver looks like this:
public class FormCollectionValueResolver<TDestination>:ValueResolver<FormCollection,TDestination>
{
//Code removed for brevity
}
The application compiles and runs, however without the custom resolver, nothing comes into the object, it just creates a mock object with exception throwing get accessors.
The reason the FormCollectionValueResolver<Customer> never gets called is that the ForAllMembers() method iterates over all your property mappings, as defined by the ForMember() method, applying the specified member options. However, in the code sample you supplied no property mappings have been defined, thus the resolver never gets called.
Here is an example of how the ForAllMembers() method could be used.
[Test]
public void AutoMapperForAllMembersTest()
{
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Sum,
opt => opt.ResolveUsing<AdditionResolver>())
.ForMember(dest => dest.Difference,
opt => opt.ResolveUsing<SubtractionResolver>())
.ForAllMembers(opt => opt.AddFormatter<CustomerFormatter>());
Source source = new Source();
source.Expression = new Expression
{
LeftHandSide = 2,
RightHandSide = 1
};
Destination destination = Mapper.Map<Source, Destination>(source);
Assert.That(destination.Sum, Is.EqualTo("*3*"));
Assert.That(destination.Difference, Is.EqualTo("*1*"));
}
public class Expression
{
public int LeftHandSide { get; set; }
public int RightHandSide { get; set; }
}
public class Source
{
public Expression Expression { get; set; }
}
public class Destination
{
public string Sum { get; set; }
public string Difference { get; set; }
}
public class AdditionResolver : ValueResolver<Source, int>
{
protected override int ResolveCore(Source source)
{
Expression expression = source.Expression;
return expression.LeftHandSide + expression.RightHandSide;
}
}
public class SubtractionResolver : ValueResolver<Source, int>
{
protected override int ResolveCore(Source source)
{
Expression expression = source.Expression;
return expression.LeftHandSide - expression.RightHandSide;
}
}
public class CustomerFormatter : IValueFormatter
{
public string FormatValue(ResolutionContext context)
{
return string.Format("*{0}*", context.SourceValue);
}
}
You should consider ditching FormCollection altogether:
Link
Basically, you'll lean on strongly-typed views + custom-created ViewModel types for forms. These forms have things like validation attributes on them so you can run them through validation frameworks. If it's valid, only then do you update your persistence model from the posted form. We stay away from creating domain objects directly from the posted form.

Resources