ASP.NET MVC Dropdownlist - c#

I have:
public partial class Ingredient
{
public int IngredientID { get; set; }
public string IngredientName { get; set; }
}
in controller:
IEnumerable<Ingredient> ListOfIngridient = FRE.Ingredient.Select(key => key).ToList();
ViewBag.list = new SelectList(ListOfIngridient);
The main problem is that subscribtion of drop down label is wrong
I want to place there 'IngredientName'. How to fix it?

When you use this constructor:
SelectList(ListOfIngridient)
The SelectList doesn't know what is a "text" or "value" field in the supplied collection. So it simply defaults to .ToString() for both. And the default value for .ToString() for an object (unless overridden in your class, which it isn't) is the name of the class.
So both the text and the value for that select in the resulting view are the string representation of each object in the collection.
You can use a different constructor to supply it with the "text" and "value" fields to use:
SelectList(ListOfIngridient, "IngredientID", "IngredientName")

You need to use a different overload when creating your select list. There is one you can use to tell it which properties to use for the names and values.
var ingredients = FRE.Ingredient.ToList();
ViewBag.list = new SelectList(ingredients, nameof(Ingredient.IngredientID),
, nameof(Ingredient.IngredientName));
You should use the nameof operator to avoid magic strings. This has several advantages over manually putting in the strings "IngredientID" and "IngredientName". If you ever use your IDE's refactoring tools to change the names of those properties, they'll automatically be updated. Also, you get Intellisense when typing them, removing the chance of making a typo.
Additionally, grabbing the ingredients can be simplified by removing the Hungarian notation. Hungarian notation goes against Microsoft's C# style guides. Also, you can use the implicit operator var instead of manually declaring what type your ingredients are. Also, there's no need for the select statement. Select statements are used to transform the item in your LINQ query, but you weren't transforming anything.
Lastly, you're passing this information via ViewBag. You should avoid ViewBag if at all possible. It's much better to pass things in a strongly typed model.

Related

Odata - How to filter on collection property of type enum

I'm attempting to filter my entities on a property which is a collection of enum variants.
If the collection contains the variant I'm interested in, I want to return that entity.
My backing store is Cosmos DB and currently I'm storing the enum variants as an array of string (I'm not tied to that).
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum FsmoRole
{
RidMaster,
PdcEmulator,
// Other FSMO roles
}
public class DomainController
{
public IList<FsmoRole> FsmoRole { get; set; } =
new List<FsmoRole>():
}
The query I was attempting to execute was the following:
$filter=fsmoRoles/any(f: f eq RidMaster)
This returns the following error from the Odata API:
Microsoft.OData.ODataException: Could not find a property named 'RidMaster' on type 'DomainController'
And, if I use quotes around the variant I'm interested I get back no results:
$filter=fsmoRoles/any(f: f eq 'RidMaster')
One solution I could use instead is to use a collection of string's to represent the FSMO roles. This would allow me to do the following:
$filter=fsmoRoles/any(f:contains(f, 'RidMaster'))
And this works and is a viable option, but I wanted to know if I could perform this query without having to change the entities FsmoRoles property to a collection of string's.
The issue is, you're storing the enum variants as string's in the backing store.
If you look at the generated Expression tree, you'll see that it's converting your input values to Int32 and then attempting the comparison in the DB.
.Where($it => $it.FsmoRoles.Any(f => (Convert(f, Int32) == 2)))
Last time I checked the value 2 is not equal to "RidMaster". This is why you're not seeing results.
I would stop storing the collection of FsmoRoles as string's and instead store them as int's.
When returning the results from your controller you can serialize the enum variants to string.
Another option (if you don't want to store the FsmoRoles as int's) is to use the cast operator.
Your query would then look something like this:
$filter=fsmoRoles/any(f: cast(f, Edm.String) eq 'RidMaster')

DisplayTemplate being ignored (covarient interface?)

This is a weird one. I have the following view file (Views/Search/Submit.cshtml):
#model IEnumerable<KeyValuePair<string, ISearchProvider>>
#foreach (var provider in Model)
{
var results = provider.Value.Results.Take(10);
if (results.Count() > 0)
{
<text><li class="dropdown-header">#provider.Key</li></text>
#Html.DisplayFor(x => results)
}
}
... where results is a System.Collections.Generic.IEnumerable<out T>, and T is ISearchMatch.
I have then defined a display template in Views/Search/DisplayTemplates/SiteSearchMatch.cshtml;
#model SiteSearchMatch
<li>#Html.ActionLink(Model.Name, "details", "site", new { Id = Model.Id }, null)</li>
... and SiteSearchMatch implements ISearchMatch like so;
public class SiteSearchMatch: ISearchMatch
{
public int Id { get; set; }
public string Name { get; set; }
}
I'd expect that my display template gets used; but it doesn't. Instead, the output I see being output is;
<li class="dropdown-header">sites</li>
11147166811481897189813271028
... where that string of numbers is the combination of all the Ids of the ISearchMatch's I wanted to render via the display template.
It seems Razor is simply rendering the ISearchMatch using the first attribute defined in the class; if I remove the definition of the Id property, I instead see the combination of all the Name's of the ISearchMatch's.
Does anyone know why this is happening, and how I can get Razor to use the display template I've specified?
Your expectation is wrong:
I'd expect that my display template gets used; but it doesn't.
The output you see is the ID's simply listed. I suspect your ISearchMatch-interface does only expose the Id-property, but this does not matter. What matters is the actual type of the instance of the result. In your case the following line:
#Html.DisplayFor(x => results)
can be implicitly evaluated as
HtmlHelper<IEnumerable<KeyValuePair<string, ISearchProvider>>>
.DisplayFor<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>>
(Func<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>> expression);
Looks pretty complex, but basically it's just a implicit substitution of your model and expression result. Your model is of type IEnumerable<KeyValuePair<string, ISearchProvider>>. That's also the type for the input of your lampda-expression. The result is of type IEnumerable<ISiteMatch>. And here come's the important thing!
The DisplayFor implementation checks, if the result type is enumerable or not. If not, it searches for a fitting template for the type, otherwise it will iterate through the elements and does this for all elements. 1
Searching for a template works based on the type name. In your case the template uses the name of the enumerated type, which is ISearchMatch. It does not find any display template, so it simply dumps the properties, resulting in what you see:
11147166811481897189813271028
To fix this problem, you need to convert your result set to the correct type first. You can do this in different ways. Either you cast the whole result of your provider results:
var results = provider.Value.Results
.Cast<SiteSearchMatch>()
.Take(10);
or you cast them individually within your lamda expression:
#Html.DisplayFor(x => (SiteSearchMatch)results)
The important thing is, that the scalar result type is the same as the model in your display template.
1 Note that this is a little bit more complex, for example the the extension also keeps track of an index and applys it to the output, so that the model could be bound for postback purposes.
The lame answer is that the "Build Action" on my View file Views/Search/DisplayTemplates/SiteSearchMatch.cshtml was set to "None", rather than "Content".
This meant the code worked fine when running in Debug mode within Visual Studio, but didn't work when any deployment was made.
Just to reiterate; this fix required no code changes. Simply change the "Build Action" back to "Content".

Is it possible to create a class template and select a C# property when the function is called?

Does anyone know if the following is possible to pass in a List<> of objects to a function and specify which property the function should use within each object that its working with ?
I have a class that works with a specific property of an object throughout the class, but I dont want to create multiple copies of the same class to work with different properties of that object. I thought about using Linq, but I dont see a way to specify which property to use in other functions of the manipulation class.
I was thinking there has to be a more elegant way to do this instead of creating the same class to handle each property. I thought about using Reflection to tell the function which property to work with but that gets ugly really quick
Example psuedo code :
class Store
{
int amount;
int id;
int serial;
}
class AggregationMethods
{
bool Has3Values( List<Store> places /* some other param to specify which property to use*/)
{
// do something with Store.amount or Store.id
}
// other functions to work with Store.amount or Store.id or Store.serial
}
In your case, they're all int values - so you could just retain a Func<Store, int> or pass it into each method. It becomes slightly harder if you need to work over multiple types, but we don't really have enough information to comment further.
It's also not clear whether you would expect two have multiple instances of AggregationMethods (e.g. one for amounts, one for IDs etc) or whether these would really be static methods. If you're using instances, then you could keep the projection as a member variable, and apply it within each method.
It's worth noting that the properties you've given probably don't really make sense to apply the same aggregations - for example, while summing amounts makes sense, it's meaningless to sum IDs.

how to iterate through reader for notnull without repeating code over and over

First off, I am new to programming (especially with C#) and thanks for your help.
I have a static web form with about 150 form objects (most checkboxes). I decided to go 1 record per form submission in the sql db. So, for example, question X has a choice of 5 checkboxes. Each of these 5 checkboxes has a column in the db.
I have the post page complete(working) and am building an edit page where I load the record and then populate the form.
How I am doing this is by passing a stored proc the id and then putting all the returned column values into the according object properties, then setting the asp control object to them.
An example of setting the asp controls to the selected value:
questionX.Items[0].Selected = selectedForm.questionX0
questionX.Items[1].Selected = selectedForm.questionX1
questionX.Items[2].Selected = selectedForm.questionX2
As you see, this is very tiresome since there are over 150 of these to do. Also, I just found out if the response is NULL then I get the error that it cant be converted to a string. So, I have added this line of code to get past it:
This is the part where I am populating the returned column values into the object properties (entity is the object):
if (!String.IsNullOrEmpty((string)reader["questionX0"].ToString()))
{entity.patientUnderMdTreatment = (string)reader["questionX0"];}
So, instead of having to add this if then statement 150+ times. There must be a way to do this more efficiently.
First of all, it seems that you are using string.IsNullOrEmpty(value), but this won’t check for the special DBNull value that is returned from databases when the data is null. You should use something more akin to value is DBNull.
The rest of your problem sounds complex, so please don’t be put off if my answer is complex too. Personally I would use custom attributes:
Declare a custom attribute
The following is a skeleton to give you the idea. You may want to use the “Attribute” code snippet in Visual Studio to find out more about how to declare these.
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public sealed class QuestionColumnAttribute : Attribute
{
public string ColumnName { get; private set; }
public QuestionColumnAttribute(string columnName)
{
ColumnName = columnName;
}
}
Use the custom attribute in the entity class
Where you declare your entity class, add this custom attribute to every field, for example where patientUnderMdTreatment is declared:
[QuestionColumn("questionX0")]
public string patientUnderMdTreatment;
Iterate over the fields
Instead of iterating over the columns in the reader, iterate over the fields. For each field that has a QuestionColumnAttribute on it, get the relevant column from the reader:
foreach (var field in entity.GetType().GetFields())
{
var attributes = field.GetCustomAttributes(typeof(QuestionColumnAttribute), true);
if (attributes.Length == 0)
continue;
object value = reader[attributes[0].ColumnName];
if (!(value is DBNull))
field.SetValue(entity, value.ToString());
}
For the first part of your question where you set the ASP controls, you can use a similar strategy iterating over the fields of selectedForm, and this is probably simpler because you don’t need a custom attribute — just take only the fields whose name starts with “questionX”.
this is a quick & easy way of doing it.. there are some suggestions to investigate LINQ, and I'd go with those first.
for (int i = 0; i < 150; i++)
{
if (!String.IsNullOrEmpty((string)reader["questionX" + i.ToString()].ToString()))
{entity.patientUnderMdTreatment = (string)reader["questionX" + i.ToString()];}
}
... though this wouldn't be any good with the
questionX.Items[0].Selected = selectedForm.questionX0
questionX.Items[1].Selected = selectedForm.questionX1
questionX.Items[2].Selected = selectedForm.questionX2
lines
so I hear two questions:
- how to deal with null coming from IDataReader?
- how to deal with multiple fields?
Lets start with simple one. Define yourself a helper method:
public static T IsDbNull<T>(object value, T defaultValue)
{
return (T)(value is DBNull ? defaultValue : value);
}
then use it:
entity.patientUnderMdTreatment = IsDbNull<string>(reader["question"], null);
Now how to map entity fields to the form? Well that really is up to you. You can either hardcode it or use reflection. The difference of runtime mapping vs compile-time is likely to be completely irrelevant for your case.
It helps if your form fields have identical names to ones in the DB, so you don't have to do name mapping on top of that (as in Timwi's post), but in the end you'll likely find out that you have to do validation/normalization on many of them anyway at which point hardcoding is really what you need, since there isn't a way to dynamically generate logic according to the changing spec. It doesn't matter if you'll have to rename 150 db fields or attach 150 attributes - in the end it is always a O(n) solution where n is number of fields.
I am still a little unsure why do you need to read data back. If you need to preserve user's input on form reload (due to validation error?) wouldn't it be easier/better to reload them from the request? Also are entity and selectedForm the same object type? I assume its not a db entity (otherwise why use reader at all?).
Its possible that there are some shortcuts you may take, but I am having hard time following what are you reading and writing and when.
I recommend using the NullableDataReader. It eliminates the issue.

Downcasting in C#

I'm facing a problem that I don't know how to solve and am hoping the community can help.
I'm writing an app that manages "Lead" objects. (These are sales leads.) One part of my program will import leads from a text file. Now, the text file contains lots of potential leads, some of which I will want to import and some of which I won't.
For ease of programming (and use), I'm parsing the text file into a List<Lead> object, and using a DataGridView to display the leads by setting the DataSource property of the DataGridView.
What I want to do is add a column to the grid, called "Import," with a checkbox that the user can check to indicate whether or not each lead should be imported.
My first thought is to derive a class from Lead:
public Class LeadWithImportCheckbox : Lead
{
bool bImport = false;
public bool Import
{
get { return bImport;}
set { bImport = value;}
}
}
However, the parsing engine returns a list of Lead objects. I can't downcast a Lead to a LeadWithImportCheckbox. This fails:
LeadWithImportCheckbox newLead = (LeadWithImportCheckbox)LeadFromParsingEngine;
This is an invalid cast.
The other option I see is to create a constructor for LeadWithImportCheckbox:
public LeadWithImportCheckbox(Lead newlead)
{
base.Property1 = newlead.Property1;
base.Property2 = newlead.Property2;
....
base.Property_n = newlead.Property_n;
}
This is problematic for two reasons. One, the Lead object has several dozen properties and writing this constructor is a PITA.
But worse, if I ever change the underlying structure of Lead, I need to remember to go back and change this constructor for LeadWithImportCheckbox. This is a danger to my code maintenance.
Is there a better way of accomplishing my goal?
or, to avoid the PITA aspect, use reflection... (try this...)
EDIT: use property, not Field as I had originally written...
public class NewLead : Lead
{
public bool Insert;
public NewLead(Lead lead, bool insert)
{
Insert = insert;
foreach (PropertyInfo pi in typeof(Lead).GetProperties())
GetType().GetProperty(pi.Name).SetValue
(this, pi.GetValue(lead,null), null);
}
}
public class LeadListItem
{
public Lead Lead { get; set; }
public bool ShouldImport { get; set; }
}
i.e. don't copy the Lead object's contents, just store a reference to it in a new LeadListItem object, which adds extra info "outside" the original object.
If you want the properties of Lead to appear in the grid, there is almost certainly a way of doing that. Why not ask that question, instead of downvoting me for telling you the right answer to this question!
A couple options you might have missed:
You could update the Lead object itself to have an Import property (that defaults to false).
You could have your "ImportLead" object treat the Lead as payload (even make it generic, if you want), so you don't need the big constructor.
Build a new Lead object list or enumerable that only contains the objects you want to import in the first place.
You can only downcast, if the object to be downcast is really an object of that type.
An easier way to solve your problem would be to have a DisplayLead class, such as:
public class DisplayLead {
Lead lead;
bool bImport;
}
which would also help you separating stored data from their representation in a GUI.
What you want to do is display the checkbox column on your grid and not have it related at all to your Lead objects. You use the marked columns (and possible the original List) to build a new set of List which will be your import list.
Then handle whatever you wish to do with the newly created List.
Edit: One thing to be careful of when working with lists is the fact every class object is actually only a pointer to the class so if you work with the original list and do something like:
List<Lead> Importable = new List<Lead>();
for(int i=0, i++, i<viewGrid.Count)
if(viewGrid[i].CheckedColumn.Checked)
Importable.Add(OriginalList[i]);
That objects will exist in both lists and if you edit data of a Lead on either list both will be changed.
I cannot downcast to something it is not. If the object was instantiated as a Lead, then it can't be downcast to any derived class. If it were instantiated as a LeadWithImportCheckbox and then returned to your code as Lead, then you can downcast it.
Protip: Check type at runtime with is operator.
There are many ways to do this, but the "right" way pops out because of what you said, here:
For ease of programming (and use), I'm
parsing the text file into a
List object, and using a
DataGridView to display the leads by
setting the DataSource property of the
DataGridView.
What I want to do is add a column to
the grid, called "Import," with a
checkbox that the user can check to
indicate whether or not each lead
should be imported.
Your Lead object stands well on its own, and you want to attach some metadata to it -- you don't want to create another Lead classification (i.e. the LeadWithImportCheckbox class).
So, the best approach in your case is to have a class like so:
public class LeadInfo
{
private Lead lead;
private bool shouldImport;
public LeadInfo(Lead lead)
{
this.lead = lead;
this.ShouldImport = false;
}
public bool ShouldImport
{
get { return shouldImport; }
set { shouldImport = value; }
}
}
This will scale well when you want to add more metadata to your list, like if you want to send yourself email reminders about them every week.
I've seen the correct solution listed so many times I feel like a heel posting it again, but the best way to approach this is to write a wrapper for the Lead object that includes the import flag.
If the properties of the Lead object don't appear in the GridView because you're databinding to the object, then write passthrough properties that mirror the Lead properties on the wrapper object.
The issue is that you want something displayed to the user that isn't an inherent part of the data model. The answer is to wrap the data before presenting it to the user so you can control what they see without changing the underlying model.
If you're concerned that the Lead object will change so many times in the future that changes to the wrapper will be cumbersome, you could look into dynamic code generation based on the Lead object that will automatically generate a wrapper object with the same fields as the Lead object plus the import flag. Though frankly, that's a lot more work than you'll probably need for something as straightforward as this.
As a quick and dirty solution, you can create your 'checkbox' object as a different object that contains an instance of Lead.
public GridLead {
public bool Import { get; set; }
public Lead Lead { get; set; }
}
This way you can easily add more 'grid' properties to this object, while still always retaining a reference to the Lead details without hardcoding property cloning into it.
Recommend you try modifying (upgrading) your imported lead objects.
Try starting with the examples here...
If your Lead class had a copy constructor (e.g. "Lead(Lead otherLead)"), LeadWithImportCheckbox would inherit that and you could just call the base Lead constructor in the LeadWithImportCheckbox constructor - hence no need for LeadWithImportCheckbox to be aware of the details of Lead.

Categories

Resources