ScriptIgnore attribute not working on partial class - asp.net-mvc

I have a problem using the ScriptIgnore tag on my partial view to stop a property from serializing.
var docs = #Html.Raw(Json.Encode(Model))
The funny thing is, when I add the attribute directly to the partial class in the .tt file, it works as expected, but because that file will be overwritten when I do a code-gen, I tried using MetadataType
[MetadataType(typeof(DocumentMeta))] //this is added so we can add meta data to our partial class..
public partial class Document
{
}
[MetadataType(typeof(DocumentCategoryMeta))] //this is added so we can add meta data to our partial class..
public partial class DocumentCategory
{
}
public class DocumentMeta
{
[ScriptIgnore] //We add the scriptignore here because we are serializing some of these entities in client code
public virtual ICollection<DocumentCategory> DocumentCategories { get; set; }
}
public class DocumentCategoryMeta
{
[ScriptIgnore] //We add the scriptignore here because we are serializing some of these entities in client code
public virtual DocumentCategory Parent { get; set; }
}
I still get the same error:
A circular reference was detected while serializing an object of type 'DocumentCategory'.
Because DocumentCategory contains hierarchical data.
Any help would be greatly appreciated!
Tribe84

try [ScriptIgnore(ApplyToOverrides = true)]

For all virtual Properties you should use the ScriptIgnore attribute, like this: [ScriptIgnore(ApplyToOverrides = true)]

Related

MVC Razor helpers do not render proper ID and Name attributes for fields of interface derived classes

I have a class which looks like this:
public class ApplicationFormModel
{
protected ApplicationFormModel()
{
CurrentStep = ApplicationSteps.PersonalInfo;
PersonalInfoStep = new PersonalInfo();
}
public PersonalInfo PersonalInfoStep { get; set; }
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
where IEducationalBackground, IAboutYou, and IOther are interfaces. I do not use this class directly, but I use derived classes of this one which upon instantiation create the proper instances of EducationalBackgroundStep, AboutYouStep, and OtherStep.
In my view, I am using Razor Helpers such as
#Html.TextBoxFor(model => (model.EducationalBackgroundStep as ApplicationFormModels.EducationalBackgroundAA).University, new {#class = "form-control", type = "text", autocomplete = "off"})
The field 'University', for example, is NOT part of the Interface and I therefore need the cast to access it. Everything is fine for properties of the interface itself, but those which I need to cast for do not end up having the correct ID and Name properties.
For example, instead of EducationalBackgroundStep_University as ID, I only get University. This causes the form to not include this value when submitting it.
I did not have this issue before when I used a base class instead of an interface, but then I had to include the EducationalBackgroundStep, AboutYouStep, and OtherStep in each derived class (and have it then of the correct derived type), but that is what I wanted to avoid.
Is there any way around this? Thank you very much!
The issue with the ID generation is because you are using casting (x as y) and the TextBoxFor expression handler can't determine what the original model property was (more to the point, it doesn't make sense to use the original model property as you're not using it any more, you're using the cast property)
Example fiddle: https://dotnetfiddle.net/jQOSZA
public class c1
{
public c2 c2 { get; set; }
}
public class c2
{
public string Name { get; set; }
}
public ActionResult View(string page, bool pre = false)
{
var model = new c1 { c2 = new c2 { Name = "xx" } };
return View(model);
}
View
#model HomeController.c1
#Html.TextBoxFor(x=>Model.c2.Name)
#Html.TextBoxFor(x=>(Model.c2 as HomeController.c2).Name)
The first textboxfor has ID c2_Name while the second has just Name
You have two options:
1) use concrete classes rather than interfaces for your viewmodel
2) don't use TextBoxFor and instead use TextBox and specify the ID manually (but then you'll lose refactoring)
#Html.TextBox("c2_Name", (Model.c2 as HomeController.c2).Name)
This will give you the ID you're expecting, but as #StephenMuecke rightly points out, this might not bind correctly when you do the POST - so you may still be stuck... but at least it answers the question.
#freedomn-m explained to me why my code wouldn't work and he put me on the right track to find a solution, so he gets the accepted answer.
The workaround I used is the following - so I now have the following classes:
public class ApplicationFormViewModel {
public PersonalInfo PersonalInfoStep { get; set; }
// constructors which take the other classes and
// initialize these fields in an appropriate manner
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
// in our case, XX can be one of 3 values, so we have 3 classes
public class ApplicationFormXX {
public PersonalInfo PersonalInfoStep { get; set; }
// constructor which take the ApplicationFormViewModel and
// initialize these fields in an appropriate manner
public EducationalBackgroundXX EducationalBackgroundStep { get; set; }
public AboutYouXX AboutYouStep { get; set; }
public OtherXX OtherStep { get; set; }
}
To the main View I send the ApplicationFormViewModel and for each of the fields, I call a separate Partial View.
The Partial views render the common fields which are present in the Interfaces and then, depending on the type of the object held by the interface, it calls a different partial view which accepts the correct Model.
Example:
In the main View I have (NOTE: The actions return a partial view):
#model Applications.Models.ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#Html.Action("RenderEducationalBackgroundStep", "ApplicationFormsLogic", routeValues: new {model = Model})
In the Partial View of for the EducationalBackgroundStep, I have:
#model ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#{
var educationalBackgroundType = Model.EducationalBackgroundStep.GetType();
if (educationalBackgroundType == typeof(EducationalBackgroundXX))
{
<text>#Html.Partial("~\\Views\\Partials\\ApplicationForm\\Partials\\ApplicationSteps\\EducationalBackground\\_EducationalBackgroundXX.cshtml", new ApplicationFormModels.ApplicationFormModelXX { EducationalBackgroundStep = Model.EducationalBackgroundStep as EducationalBackgroundXX })</text>
}
// OTHER ELSE IF CASES
}
And then, the _EducationalBackgroundXX.cshtml partial view expects a model like this:
#model ApplicationFormModels.ApplicationFormModelXX
This way, no casting is required and everything works fine with the ModelBinder. Again, thank you #freedomn-m for setting me on the right track.
NOTE: In practice I need more fields than the ones presented here (for navigation and some custom logic), so actually all of these classes inherit an abstract base class (this makes it redundant to have the PersonalInfoStep declared in each of the classes, for example, because it can be inherited from the abstract base class). But for the intents and purposes of this method, what's present here suffices.

How do I bind multiple properties in an Android layout element

I'm using MvvmCross to databind my ViewModel to an Android View layout.
From the SimpleBinding example I can see that to bind a value to a property I do this:
<EditText
android:hint="Subtotal"
android:gravity="left"
android:inputType="numberDecimal"
android:maxLines="1"
android:numeric="decimal"
local:MvxBind="{'Text':{'Path':'SubTotal','Converter':'Float'}}"
/>
so Text is bound to the SubTotal property of the ViewModel. But how do I bind to more than one property? In my case I want to bind a ViewModel property called HigherLower to the TextColor attribute of the layout element. I can't add another MvxBind and I can't set MvxBind to an array.
The format of the JSON used in the binding expression is a Dictionary of named MvxJsonBindingDescriptions
public class MvxJsonBindingDescription
{
public string Path { get; set; }
public string Converter { get; set; }
public string ConverterParameter { get; set; }
public string FallbackValue { get; set; }
public MvxBindingMode Mode { get; set; }
}
This is used with:
the dictionary Key name being the target (View) property for the binding.
the binding Path property being the source (DataContext) property for the binding - if Path is not specified then the whole DataContext itself is the binding source.
For Activity/View level axml the DataContext is the ViewModel - but for sub-View axml then the DataContext will normally be a child object of the ViewModel - e.g. inside a ListView the DataContext might be an item inside a List or ObservableCollection owned by the ViewModel.
To specify multiple bindings you can use JSON like:
{
'TargetProperty1':{'Path':'SourceProperty1'},
'TargetProperty2':{'Path':'SourceProperty2'}
}
For your particular example this might be:
local:MvxBind="
{
'Text':{'Path':'SubTotal','Converter':'Float'},
'TextColor':{'Path':'HigherLower','Converter':'MyColorConverter'}
}"
where your ViewModel is something like:
public class MyViewModel : IMvxViewModel
{
public float SubTotal { get; set; }
public bool HigherLower { get; set; }
// more code here
}
and your converter is something like:
public class MyColorConverter : MvxBaseColorConverter
{
protected override MvxColor Convert(object value, object parameter, CultureInfo culture)
{
return ((bool)value) ? new MvxColor(255,0,0) : new MvxColor(0,255,0);
}
}
and where that converter is initialized during Setup - e.g. see how the properties of the Converters class are used in TwitterSearch
One sample that shows Multiple Bindings at work is BestSellers - see Click and Text bound in the list item https://github.com/slodge/MvvmCross/blob/master/Sample%20-%20BestSellers/BestSellers/BestSellers.Droid/Resources/Layout/ListItem_Category.axml
Path':'HigherLowerYou must do this:
local:MvxBind="{'Text':{'Path':'SubTotal','Converter':'Float'}, 'TextColor':{'Path':'HigherLower','Converter':'Color'}}"
Note the:
bind="{ 'Text':{xx}, 'Other':{yy} }"

Display template not used for interface

I'm having a problem with display templates and dealing with interfaces and objects which implement the interface. In the example I have many objects, which I want to be rendered in a fixed way, I decided to create an interface and reference this in the view which I've decided to put into the shared display templates folder. DisplayFor doesn't seam to work for objects passed to it which implement the interface in the view, does any one know a solution to this.
Its probably easier to explain via code so I've wrote a quick example. The base interface and two classes which inherit from it:
public interface IPet
{
String Name { get; }
}
public class Dog : IPet
{
public String Name { get; set; }
}
public class Cat : IPet
{
public String Name { get; set; }
}
The example display template in shared display templates
#model IPet
<div>#Model.Name</div>
The example view model to be passed to the view
public class VM
{
public IPet Master { get; set; }
public IEnumerable<IPet> Minions { get; set; }
}
The controller (in this case to create mock information)
public ActionResult Index()
{
var viewModel = new VM();
viewModel.Master = new Cat(){Name = "Fluffy"};
var minions = new List<IPet>();
minions.Add(new Dog(){Name = "Dave"});
minions.Add(new Dog(){Name = "Pete"});
minions.Add(new Cat(){Name = "Alice"});
viewModel.Minions = minions;
return View(viewModel);
}
and finally the view which I would expect DisplayFor to work
#model ViewInheritance.Models.VM
<h2>Master</h2>
#Html.DisplayFor(x => x.Master)
<h2>Minions</h2>
#Html.DisplayFor(x => x.Minions)
Given that all the objects are are defined in the view model as the interfaces, howcome it fails to use the display template?
One solution I have found is to simply use the code
#Html.DisplayFor(x => x.Master, "IPet")
To recap, the question is:
Why does this happen?
Is there a way to make DisplayFor correctly work out that a type of Cat which implements IPet should in fact be looking at the common shared view IPet.cshtml?
Thanks
Starting a new MVC application and fixing the code to actually compile the view renders fine. It also renders fine when moving the view into the shared folder.
I Added setter to IPet:
public interface IPet
{
String Name { get; set; }
}
I updated implementation and added public accessors:
public class Dog : IPet
{
public String Name { get; set; }
}
public class Cat : IPet
{
public String Name { get; set; }
}
I left your VM alone and also did not change any code in your View.
Pressing F5, running the MVC application rendered the results as expected (See image).
Unfortunately, I don't think ASP.NET MVC currently supports automatically selecting templates based on implemented interfaces. I think this makes sense because a class could implement multiple interfaces, so if you had templates for more than one of those interfaces, which one should the framework choose?
You could use a base class instead of an interface if your design can cope with it:
Change IPet to a (possibly abstract) class.
Change IPet.cshtml to Pet.cshtml.
Otherwise I think you'll just need to explicitly tell the framework which template to use. Here are some options:
Decorate the view model properties with [UIHint].
Specify the template in your calls to your HtmlHelper methods such as DisplayFor.
Make your own ModelMetadataProvider and change the TemplateHint property of the resulting ModelMetadata.

MVC Validation Attribute

How do you validate a class using Validation attributes when validating strongly typed view models.
Suppose you have a view model like so:
[PropertiesMustMatch("Admin.Password", "Admin.ConfirmPassword")]
public class AdminsEditViewModel
{
public AdminsEditViewModel()
{
this.Admin = new Admin(); // this is an Admin class
}
public IEnumerable<SelectListItem> SelectAdminsInGroup { get; set; }
public IEnumerable<SelectListItem> SelectAdminsNotInGroup { get; set; }
public Admin Admin { get; set; }
}
I get null exception when on this line of PropertiesMustMatchAttribute
object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
since Password field is a property of Admin class and NOT AdminsEditViewModel. How do I make it so that it will go so many levels deep until it does find property of Admin in the ViewModel AdminsEditViewModel?
thanks
You need to modify the PropertiesMustMatchAttribute class to parse the property name and search deeply.
This attribute is not part of the framework; it's included in the default MVC template (in AccountModels.cs)
You can therefore modify it to suit your needs.
Specifically, you would call name.Split('.'), then loop through splitted names and get the property values.
It would look something like
object GetValue(object obj, string properties) {
foreach(strong prop in properties)
obj = TypeDescriptor.GetProperties(obj)
.Find(prop, ignoreCase: true)
.GetValue(obj);
}
return obj;
}

ASP.NET MVC Model properties not in view returning null

I have an EF Code First model that has an ICollection property like so.
public class AccountProfile
{
[Key]
public int AccountID { get; set; }
public string AccountName { get; set; }
public string Email { get; set; }
[Required]
public virtual ICollection<UserProfile> LinkedUserProfiles { get; set; }
}
I'm binding an edit view to this model, but it only shows the AccountName and Email properties.
I have a HttpPost ActionResult to update the model that takes an AccountProfile.
When posting, the AccountProfile object only has the AccountName and Email properties populated. The LinkedUserProfiles is null. This means that the model cannot be updated as the LinkedUserProfiles property is required.
I have also tried something like the following without any luck
var curAccountProfile = _accountProfileRepository.GetById(accountProfile.AccountID);
TryUpdateModel(curAccountProfile);
What am I doing wrong? How should this circumstance be handled in MVC?
UPDATE
The data was coming from a repository eg.
var accountProfile = _accountProfileRepository.ById(1);
return View(accountProfile);
Inspecting the accountProfile object before the view was loaded shows that the collection is being retrieved - so it's not a case of lazy loading not working as expected.
I have since implemented AutoMapper, created a ViewModel for the view, and changed my code to be something like this:
var accountProfile = _accountProfileRepository.ById(accountInformation.AccountID);
var result = _mappingEngine.Map<DisplayAccountInformationViewModel, AccountProfile>(accountInformation, accountProfile);
_unitOfWork.Commit();
It's working as expected now - but it seems like more effort than it should be?
Try eager loading the LinkedUserProfiles:
var curAccountProfile = _accountProfileRepository
.GetById(accountProfile.AccountID)
.Include(ap => ap.LinkedUsedProfiles);
Looks like you're using the Repository pattern, so not sure how you handle eager loading. My Repositories return IQueryable<T>, for which the Include extension methods works off.
If you're not using IQueryable<T>, then you might need to do the eager loading inside the Repository (such as accepting a boolean flag, e.g: GetById(int id, bool include profiles).

Resources