access [NotMapped] property with a custom getter/setter - asp.net-mvc

I have a database with folders where I store the whole path to a folder as a folder_name so sth. like: //MainFolder/subFolder/folderName. Im my application I have a Model called Folder, which represents the folders from db.
Model:
public class Folder
{
[Key]
public string folder_name { get; set; }
[NotMapped]
public string folder_name_short
{
get
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/")); //System.NullReferenceException here
return shortname;
}
set
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/"));
this.folder_name = folder_name.Replace(shortname, value);
}
}
}
folder_name_short isn't mapped because it is only a substring from the whole path, so I don't want to store it twice. Example want I mean:
Console.WriteLine(folder_name) output://MainFolder/subFolder/folderName
Console.WriteLine(folder_name_short) output:/folderName
In my View the user can rename the folder. So I would like to replace the old folder_name_short to the new one and store the new String in folder_name
View:
.
.
using (Html.BeginForm("Rename", "Folders", FormMethod.Post))
{
#Html.EditorFor(model => model.folder_name_short)
<input type="submit" value="rename folder" id="submit" class="btn btn-default" />
}
.
.
Problem:
The Inputtextbox renders and shows the current Value of folder_name_short in it. When I change it and click the submit button, I get a System.NullReferenceException in the Model (as marked in the source code, please scroll to right). I don't understand what is wrong and what changes are needed.
Edit:
when the setter is commented out, the Exception disappear. So maybe the setter is causing the error?
Solution:
use the standard setter and getter for storing the folder_name_shortvalue, but implement a public get/set method to set the folder_name in the db and call this method in the controller. So:
[NotMapped]
public string folder_name_short { get; set; }
public string getfolder_name_short()
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/"));
return shortname;
}
public void setfolder_name_short(string newname)
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/"));
folder_name = folder_name.Replace(shortname, newname);
}

If the value that is edited on the form is the short name folder_name_short then the model class could contain its string property:
public string folder_name_short { get; set; }
This property does not contain any logic. Any other code that contains some logic could be in the controller in an action method.
It seems you are using the same class for DB access and as a model class for the MVC view, which is the source of the problem.

[HttpPost]
[("Rename")]
public async Task<IHttpActionResult> ChangeFolderPath(Folder folder)
{
//Put a breakpoint here and make sure the folder has a value
}
probably that the setter method overwrite the value of folder_name.
I would recommand this thread to help understand custom setters
C# properties: how to use custom set property without private field?
EDIT
public class Folder
{
[Key]
public string folder_name { get; set; }
/*Model Logic shouldn't be in the Poco*/
/*You may put that in your model or best thing would be to implement a repository pattern*/
public string ShortName()
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/")); //System.NullReferenceException here
return shortname;
}
public void SetUsingShortName(string value)
{
string shortname = folder_name.Substring(folder_name.LastIndexOf("/"), folder_name.Length - folder_name.LastIndexOf("/"));
this.folder_name = folder_name.Replace(shortname, value);
}
}

Related

Get property name in Tag Helper

ASP.NET Core introduced custom tag helpers which can be used in views like this:
<country-select value="CountryCode" />
However, I don't understand how can I get model property name in my classes:
public class CountrySelectTagHelper : TagHelper
{
[HtmlAttributeName("value")]
public string Value { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
...
// Should return property name, which is "CountryCode" in the above example
var propertyName = ???();
base.Process(context, output);
}
}
In the previous version I was able to do this by using ModelMetadata:
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var property = metadata.PropertyName; // return "CountryCode"
How can I do the same in the new ASP.NET tag helpers?
In order to get property name, you should use ModelExpression in your class instead:
public class CountrySelectTagHelper : TagHelper
{
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var propertyName = For.Metadata.PropertyName;
var value = For.Model as string;
...
base.Process(context, output);
}
}
You can pass a string via the tag helper attribute.
<country-select value="#Model.CountryCode" />
The Value property will be populated by Razor with the value of Model.CountryCode by prepending #. So you get the value directly without the need to pass the name of a model property and accessing that afterwards.
I am not sure whether you got what you wanted. If you are looking to pass the complete model from view to the custom tag helper, this is how i do it.
You can pass in your current model from the view using any custom attributes. See the example below.
Assuming your model is Country
public class Country
{
public string Name { get; set; }
public string Code { get; set; }
}
Now declare a property in your custom tag helper of the same type.
public Country CountryModel { get; set; }
Your controller would look something like this
public IActionResult Index()
{
var country= new Country
{
Name = "United States",
Code = "USA"
};
return View("Generic", country);
}
In this setup, to access your model inside the taghelper, just pass it in like any other custom attribute/property
Your view should now look like something like this
<country-select country-model="#Model"></country-select>
You can receive it in your tag helper like any other class property
public override void Process(TagHelperContext context, TagHelperOutput output)
{
...
// Should return property name, which is "CountryCode" in the above example
var propertyName = CountryModel.Name;
base.Process(context, output);
}
Happy coding!

ASP.Net MVC Controller FromUri to Model - Can we define the order?

In my applicantion, I browse to the URL by supplying the parameters through query string. Based on the URI, the respective controller's action is triggered, and the parameters supplied are auto-mapped to my model.
URL: http://{host}:{port}/{website}/{controller}/{action}?{querystring}
URI:
/{controller}/{Action}?{QueryString}
My URI: Employee/Add?EmployeeCode=Code3&EmployeeId=103
EmployeeModel
public class EmployeeModel
{
public Employee()
{
}
public string EmployeeId { get; set; }
public string EmployeeCode { get; set; }
//Some more properties here
}
EmployeeController
[HttpGet]
[Route("Add")]
public IActionResult Add([FromUri] EmployeeModel model)
{
//Some code here
}
While this all works fabulous, when I browse through, below is the order in which break-points hit,
Add method of EmployeeController
Default constructor of EmployeeModel
set method of EmployeeId property of EmployeeModel
set method of EmployeeCode property of EmployeeModel
I suspect the order in which the properties get initialized is based on the order they are declared in the class.
But, to create an instance and initialize the properties the framework must be using reflection. And as per the MSDN documentation for Type.GetProperties the order is not guarateed.
The GetProperties method does not return properties in a particular
order, such as alphabetical or declaration order. Your code must not
depend on the order in which properties are returned, because that
order varies.
I basically want the initialization to take place in a specific order, is this possible?
You can't get the model binding mechanism to do things in a specific order, but you can make sure that the order is applied where it has to be.
Presumably, EmployeeModel is a domain model object on which the order actually matters, and you're now model binding directly to this type. Instead, introduce an edit model1 which you model bind to, and then map that to your model type:
public class EmployeeEditModel
{
public string EmployeeId { get; set; }
public string EmployeeCode { get; set; }
}
// and change your action signature to this:
[HttpGet]
[Route("Add")]
public IActionResult Add([FromUri] EmployeeEditModel model)
1 For an explanation of what an edit model is, see the final remarks on this old answer of mine.
To perform the mapping you have numerous alternatives, some better than others. Pick one that suits you - however, since the reason the order matters is probably something inherent in the domain model object, I'd advice you to put the logic inside it (e.g. in a constructor), to make it easier to remember to change it if the requirements change.
Map via a constructor on the model object
public class EmployeeModel
{
public EmployeeModel(string employeeId, string employeeCode /* , ... */)
{
// do stuff in whatever order you need
EmployeeId = employeeId;
EmployeeCode = employeeCode;
}
// Now your properties can be get-only
public string EmployeeId { get; }
public string EmployeeCode { get; }
}
Map via an extension method that does everything in the right order
public static class EmployeeEditModelExtensions
{
public EmployeeModel AsDomainModel(this EmployeeEditModel editModel)
{
// do stuff in whatever order you need
var model = new EmployeeModel();
model.EmployeeId = editModel.EmployeeId;
model.EmployeeCode = editModel.EmployeeCode;
// ...
}
// Now your properties can be get-only
public string EmployeeId { get; }
public string EmployeeCode { get; }
}
Use an external framework such as AutoMapper, with custom configuration to make sure that the ordering is correct
Do something else. The only purpose is to get you from an EmployeeEditModel instance to an EmployeeModel instance, assigning to the properties of the EmployeeModel in the correct order. Since you write this code yourself, you can do what you want.

MVC 4 custom data annotations read in T4 scaffolding

Can you create custom data annotations for the model that can be read inside the T4 template for the View like property.Scaffold is read? I would like to add data annotation parameters like Scaffold based on which I would build the view.
Thank you
I wrote a blog post on the solution I came up with for MVC5. I'm posting it here for anyone who comes along:
https://johniekarr.wordpress.com/2015/05/16/mvc-5-t4-templates-and-view-model-property-attributes/
Edit: In your entities, decorate property with custom Attribute
namespace CustomViewTemplate.Models
{
[Table("Person")]
public class Person
{
[Key]
public int PersonId { get; set;}
[MaxLength(5)]
public string Salutation { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(50)]
public string Title { get; set; }
[DataType(DataType.EmailAddress)]
[MaxLength(254)]
public string EmailAddress { get; set; }
[DataType(DataType.MultilineText)]
public string Biography { get; set; }
}
}
With this Custom Attribute
namespace CustomViewTemplate
{
[AttributeUsage(AttributeTargets.Property)]
public class RichTextAttribute : Attribute
{
public RichTextAttribute() { }
}
}
Then create a T4Helper that we'll reference in our template
using System;
namespace CustomViewTemplate
{
public static class T4Helpers
{
public static bool IsRichText(string viewDataTypeName, string propertyName)
{
bool isRichText = false;
Attribute richText = null;
Type typeModel = Type.GetType(viewDataTypeName);
if (typeModel != null)
{
richText = (RichTextAttribute)Attribute.GetCustomAttribute(typeModel.GetProperty(propertyName), typeof(RichTextAttribute));
return richText != null;
}
return isRichText;
}
}
}
So, this is how you do it.
Follow this tutorial on how to create a custom attribute http://origin1tech.wordpress.com/2011/07/20/mvc-data-annotations-and-custom-attributes/
To read this attribute values in the T4 scaffolding templates, first add the template files as described here http://www.hanselman.com/blog/ModifyingTheDefaultCodeGenerationscaffoldingTemplatesInASPNETMVC.aspx
Then, for example, open List.tt from the AddView folder. This template creates the Index view.
Go to the end of the template file and find the definition for class ModelProperty. Add your property value to it ( public string MyAttributeValue { get; set; }
Now go a bit down in the List.tt and find bool Scaffold(PropertyInfo property) method. You will need to add your own attribute property reader. This method, for the above mentioned tutorial, would be:
string OptionalAttributesValueReader(PropertyInfo property){
foreach (object attribute in property.GetCustomAttributes(true)) {
var attr = attribute as OptionalAttributes ;
if (attr != null) {
return attr.style;
}
}
return String.Empty;
}
Then find the method List GetEligibleProperties(Type type) at the bottom of the file. Add your reader to it like this:
...
IsForeignKey = IsForeignKey(prop),
IsReadOnly = prop.GetSetMethod() == null,
Scaffold = Scaffold(prop),
MyAttributeValue = OptionalAttributesValueReader(prop)
When you want to use and read this attribute you can do it like the Scaffold property is used in the List.tt
List<ModelProperty> properties = GetModelProperties(mvcHost.ViewDataType);
foreach (ModelProperty property in properties) {
if (property.MyAttributeValue != String.Empty) {
//read the value
<#= property.MyAttributeValue #>
}
}
Since these classes are defined in my project, I had to add my project dll and namespace to the top of the List.tt:
<## assembly name="C:\myProjectPath\bin\myMVCproject.dll" #>
<## import namespace="myMVCproject.CustomAttributes" #>
If your model changes and you need to find these new changes in the scaffolding, you need to rebuild your project.
Hope anyone looking for the solution will find this useful. Ask if there is anything unclear.
This is how I did it in MVC 5. I did this a long time ago and I may be forgetting stuff, I'm just copy/pasting what I see in my modified templates.
I needed a way to set the order of properties in (for example) the create/edit views or in the list view table. So I created a custom attribute OrderAttribute with an integer property Order.
To access this attribute in the T4 templates I modified the file ModelMetadataFunctions.cs.include.t4. At the top I added one method that retrieves the Order value set in the attribute from a PropertyMetadata object, and another method to simply order a list of PropertyMetadata items by that order:
List<PropertyMetadata> GetOrderedProperties(List<PropertyMetadata> properties, Type modelType) {
return properties.OrderBy<PropertyMetadata, int>(p => GetPropertyOrder(modelType, p)).ToList();
}
int GetPropertyOrder(Type type, PropertyMetadata property) {
var info = type.GetProperty(property.PropertyName);
if (info != null)
{
var attr = info.GetCustomAttribute<OrderAttribute>();
if (attr != null)
{
return attr.Order;
}
}
return int.MaxValue;
}
Finally, in the List template for example, I have added a part where I call the GetOrderedProperties method:
var typeName = Assembly.CreateQualifiedName("AcCtc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", ViewDataTypeName);
var modelType = Type.GetType(typeName);
var properties = ModelMetadata.Properties.Where(p => p.Scaffold && !p.IsPrimaryKey && !p.IsForeignKey && !(p.IsAssociation && GetRelatedModelMetadata(p) == null)).ToList();
properties = GetOrderedProperties(properties, modelType);
foreach (var property in properties)
{
//...
}
Unfortunately I needed the name of the project to be able to create a Type object which I needed to get the attributes from. Not ideal, perhaps you can get it some other way but I couldn't manage it without this string including all the version stuff.

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} }"

Create object with list of properties and pass it to Controller

Perhaps there is an easy solution for my problem but I simply cannot seem to find it. I have read lots of tutorials about Knockout so I get the basics but I ask this question because my entity-structure is a bit more complicated than a person with a name and a list of friends which may or may not be on Twitter (Video on Channel9: Helping you build dynamic JavaScript UIs with MVVM and ASP.NET). Here's my situation:
I have a class PersonnelClass with this basic structure:
[Serializable]
//The interface is for the implementation of 'Name' and 'Description'
public class PersonnelClass : IPersonnelClassOrPerson
{
public PersonnelClass() : this(Guid.NewGuid(), "", "") { }
public PersonnelClass(Guid id, String name, String description = null)
{
if (id == Guid.Empty) { throw new ArgumentNullException("id"); }
Id = id;
Name = name;
Description = description;
Properties = new PropertyCollection();
}
public Guid Id { get; private set; }
public String Name { get; set; }
public String Description { get; set; }
public PropertyCollection Properties { get; private set; }
}
The PropertyCollection class and associated AbstractProperty class look like this:
[Serializable]
public class PropertyCollection: List<AbstractProperty> { }
[Serializable]
public abstract class AbstractProperty: IEntity, IProperty
{
public AbstractProperty(String name, String description = null) : this(Guid.NewGuid(), name, description) { }
public AbstractProperty(Guid id, String name, String description = null)
{
if (id == Guid.Empty) { throw new ArgumentNullException("id"); }
if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); }
Id = id;
Name = name;
Description = description;
}
public Guid Id { get; private set; }
public String Name { get; private set; }
public String Description { get; private set; }
}
In my Controller, I create an instance of a PersonnelClassViewModel that has this structure:
public class PersonnelClassViewModel
{
public PersonnelClass PersonnelClass { get; set; }
public List<AbstractProperty> Properties { get; set; }
}
I fill this viewmodel with a new PersonnelClass and two test-properties to pass to my View like this:
var properties = new List<AbstractProperty>
{
new TextProperty("prop1", "descr1"),
new TextProperty("prop2", "descr2")
//TextProperty is derived from AbstractProperty
};
var vm = new PersonnelClassViewModel { Properties = properties };
return View(vm);
I get everything in my View as I wanted. From the View I want to create a new PersonnelClass with a set of selected properties. I have the fields for Name and Description and to add the properties I have a ListBox with the properties that already exist (for demo-purpose they came from the controller now). Through a bit of Knockout JavaScript code I can select items from this list and populate an HTML select-control () with the selected properties to add to the PersonnelClass. This all works fine, until I want to build up an object to pass back to the Controller and create the PersonnelClass.
My question is: what Knockout JS code is needed to build up this object and pass it to the Controller by submitting the form and in my Controller how should I receive this object, meaning: what type of object should this be (PersonnelClass, PersonnelClassViewModel, ...) ?
If any more info/code is needed, please do ask. Thanks in advance!
Update after answer of 'B Z':
I followed a few more of Steven Sanderson's tutorials about this to be sure I understand this, especially the one you provided in your answer. Now I have following code in my View to start with:
var initialData = #Html.Raw(new JavaScriptSerializer().Serialize(Model));
var viewModel = {
personnelClassViewModel : ko.mapping.fromJS(initialData),
properties : personnelClassViewModel.Properties,
selectedProperties : ko.observableArray([]),
addedProperties : ko.observableArray([])
};
ko.applyBindings(viewModel);
The variable 'initialData' contains the values I expect it to have but then I get the following error:
Microsoft JScript runtime error: 'personnelClassViewModel' is undefined
I have no clue anymore. Can anyone help me fix this?
Steven Sanderson has an example of how to to work with variable length lists and knockoutjs
http://blog.stevensanderson.com/2010/07/12/editing-a-variable-length-list-knockout-style/
Having said that, I think your problem isn't so much on the knockout side and more on the how to databind the data correctly on the server side. In the link above, Steven uses a FromJson attribute to model bind which you may find useful...
HTH

Resources