Generating Checklist from Joined Tables in MVC3 - asp.net-mvc

I am part of a team working on an MVC3 project. None of us are (yet) MVC3 experts, so I'm not even sure how to ask the question properly. I'll have to just dive in and hope it makes sense to you MVC-experienced folk.
We have an HTML data entry screen with 3 component sections: a large scrolling list on the left, a detail box in the center, and another scrolling list on the right. The left-side list is a list of group codes and names drawn from a Groups table. This is drawn by a partial view called _Groups. The center area displays the detail fields for the currently selected group. The right-side list is a scrolling list of person names. The center and right are both drawn in a partial view called _GroupDetails.
The right-side list is what's giving us problems right now. We have the Groups table, the Persons table, and an intersect table which stores a Group ID and a Person ID. If a person belongs to a group, then there's an intersect record for that person-group pair. If not, then there isn't. We want to take the currently selected Group ID and use it for a query that draws all records from the Persons table and adds a boolean field. The boolean field should be T if the person belongs to the currently-selected group, and F otherwise. Then we want to display this as a checklist with the boolean field as the checkbox field and the name next to it.
What is the best way to do this?
-- Jon
Additional information, which I hope will be useful: Our data model for this view includes this List definition:
public List<GlobalName> UserList { get; set; }
Is it possible to write a constructor for this list that includes a query, or a series of queries, that end up with the list containing the elements I want: a checkbox, the user name, and the user ID (primary key) with the checkbox set as described above?

MVC doesn't come with a CheckBoxList helper method built in as it does for SelectList. However, there are plenty of great extensions around so you don't need to reinvent the wheel. You can check here, here, or here for some examples.
Using the MvcCheckBoxList extension, for example, you can do this:
// You'll need a view model
class GroupPersonsCheckListViewModel
{
public IEnumerable<Person> Persons{ get; set; }
public Group Group { get; set; }
}
// In your controller action:
var group = context.Group.Find(id); // assumes 'id' was passed to the action
var allPersons = context.Persons.OrderBy(person => person.Name);
var groupPersons = new GroupPersonsCheckListViewModel() { Persons = allPersons, Group = group };
return PartialView(groupPersons);
// In your partial
#Html.CheckBoxListFor("GroupPersons",
m => m.Persons, // Collection of all persons
p => p.PersonId, // Person ID attribute
p => p.Name, // Person Name attribute
m => m.Group.Persons // Collection of persons that are currently associated with the group
);

Related

How to make DropDownListFor use item IDs instead of indices?

I have a table with a dropdown list in each row, like this:
#Html.DropDownListFor(m => m.Transactions[i].CategoryID, ...)
and everything mostly works. I can select items, submit the form, and the model has my updated selections. So far so good.
The problem is that this isn't very reliable. Since the name of each dropdown is based on an index rather than an ID that means the match-up between the post values and the actual items living in the database are based on indices. Most of the time that works fine, but what if the list of items changes between the time the page loads and the time a user does a postback? The indices have changed, which means the post data won't match up correctly, and bad things happen. OR I've seen the browser incorrectly try to preserve selections in dropdowns between posts, but because the list of items is changing (what may be item #2 now may be item #3 by the time the page is refreshed) and everything is based on indices, the wrong dropdowns get the wrong values.
So basically, how can I force the dropdowns to generate a name and ID that looks more like this:
Transactions_CategoryID_12385652 // 12385652 is the CategoryID
rather than this:
Transactions_4_CategoryID // 4 is the array index
and still have the benefits of automatic binding?
Edit: The second issue I mentioned (input values being restored incorrectly after a refresh) seems to only happen with Firefox. https://bugzilla.mozilla.org/show_bug.cgi?id=46845
You'd have to write your own custom model binder as well as a Html extension to generate the element names in this way, or you could generate the markup manually using Razor.
Is there a particular reason you want need to do it this way? You're almost always following the conventions of the framework unless there's a good reason not to.
You can pass the exact collection you want to the view and bind that to the DropDownFor html helper.
Say you have a Person.
public class Person
{
public int Id { get; set; }
public int Name { get; set; }
}
And you want to add a new person. Create a view model. Create a property in this view model of type SelectList. This select list will hold the collection of the model you want to populate the dropdown list with.
public class PersonViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public SelectList MySelectList { get; set; }
}
Let's say you want the drop down list to hold the Person's Id as the selected value and the Person's name as the text value. You may want to create a second view model to represent that or use an anonymous object. Let's call the collection myCollection. And let's say that the collection is made up of objects that have two properties (and Id and Name property). Now simply pass this view model with a value for MySelectList.
var viewModel = new MyViewModel
{
MySelectList = new SelectList(myCollection, "Id", "Name")
};
In your razor view you can set up the #Html.DropDownListFor like so:
#Html.DropDownListFor(m => m.Id, Model.MySelectList)
In order to pass a select value to the view for the dropdownlist simply use the SelectList constructor overload that allows this value to be passed:
new SelectList(myCollection, "Id", "Name", selectedValue)

Can I delete a single child entity without loading the entire collection?

I have 2 classes, like the below.
They can have very large collections - a Website may have 2,000+ WebsitePages and vice-versa.
class WebsitePage
{
public int ID {get;set;}
public string Title {get;set;}
public List<Website> Websites {get;set;}
}
class Website
{
public int ID {get;set;}
public string Title {get;set;}
public List<WebsitePage> WebsitePages {get;set;}
}
I am having trouble removing a WebsitePage from a Website. Particularly when removing a WebsitePage from mutliple Websites.
For example, I might have code like this:
var pageToRemove = db.WebsitePages.FirstOrDefault();
var websites = db.Websites.Include(i => i.WebsitePages).ToList();
foreach(var website in websites)
{
website.WebsitePages.Remove(pageToRemove)
}
If each website Include() 2k pages, you can imagine it takes ages to load that second line.
But if I don't Include() the WebsitePages when fetching the Websites, there is no child collection loaded for me to delete from.
I have tried to just Include() the pages that I need to delete, but of course when saving that gives me an empty collection.
Is there a recommended or better way to approach this?
I am working with an existing MVC site and I would rather not have to create an entity class for the join table unless absolutely necessary.
No, you can't... normally.
A many-to-many relationship (with a hidden junction table) can only be affected by adding/removing items in the nested collections. And for this the collections must be loaded.
But there are some options.
Option 1.
Delete data from the junction table by raw SQL. Basically this looks like
context.Database.ExecuteSqlCommand(
"DELETE FROM WebsiteWebsitePage WHERE WebsiteID = x AND WebsitePageID = y"));
(not using parameters).
Option 2.
Include the junction into the class model, i.e. map the junction table to a class WebsiteWebsitePage. Both Website and WebsitePage will now have
public ICollection<WebsiteWebsitePage> WebsiteWebsitePages { get; set; }
and WebsiteWebsitePage will have reference properties to both Website and WebsitePage. Now you can manipulate the junctions directly through the class model.
I consider this the best option, because everything happens the standard way of working with entities with validations and tracking and all. Also, chances are that sooner or later you will need an explicit junction class because you're going to want to add more data to it.
Option 3.
The box of tricks.
I tried to do this by removing a stub entity from the collection. In your case: create a WebsitePage object with a valid primary key value and remove it from Website.WebsitePages without loading the collection. But EF doesn't notice the change because it isn't tracking Website.WebsitePages, and the item is not in the collection to begin with.
But this made me realize I had to make EF track a Website.WebsitePages collection with 1 item in it and then remove that item. I got this working by first building the Website item and then attaching it to a new context. I'll show the code I used (a standard Product - Category model) to prevent typos.
Product prd;
// Step 1: build an object with 1 item in its collection
Category cat = new Category { Id = 3 }; // Stub entity
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
prd = db.Products.First();
prd.Categories.Add(cat);
}
// Step 2: attach to a new context and remove the category.
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
db.Products.Attach(prd);
prd.Categories.Remove(cat);
db.SaveChanges(); // Deletes the junction record.
}
Lazy loading is disabled, otherwise the Categories would still be loaded when prd.Categories is addressed.
My interpretation of what happens here is: In the second step, EF not only starts tracking the product when you attach it, but also its associations, because it 'knows' you can't load these associations yourself in a many to many relationship. It doesn't do this, however, when you add the category in the first step.

Asp.Net MVC, Entity Framework, Web API

I need to add dynamic controls in MVC that I have done through view but I am facing problem in inserting records. My query is there are pre-defined questions based on that answertext boxes will be generating. So each question should display corresponding to its answer.
If there are 20 questions then at run time it will generate 20 answer text boxes which means multiple records are going at once. As far as I know I will do it through DataTable please correct me if I am wrong. But how would I send data through view I am able to perform get request but unable to send multiple records and repository file as well.
Following is my schema
tblAnswer
AnswerID identity column primary key,
AnswerText varchar(500)
QuestionId foreign key
tblQuestion
QuestionId primary key identity column
QuestionText
for instance:-
#foreach(var ques in Models.questions)//questions is a list of questions
{
//enter code here
#ques.QuestionText ///will display question text
#Html.TextboxFor(model=>model.AnswerText)
<br/>
}
If i take list above then it will force me to take model[i], AnswerText then I need to pass list though I am passing list still not able to perform.
Model
public int AnswerId,
public string selectedAnswer,
public int AnswerText,
public int QuestionId,
public list<Question> questions
Selected answer is in radio button there would be 10 radio button on the screen which is also associated with other table. And questiontext does not exist in Answer table
I would handle this by creating a custom ViewModel that contained a string for the question and a string for the answer entered, then return a collection of these to bind to your view.

How can i mapped two columns and store it in temporary table MVC3 app with EF?

I have created one MVC3 application.
In which I'm selecting two Ids from two different tables
which i need to map and need to save somewhere temporary and want to display at the end.
I have taken two dropdownlist each one populated from one-one table. then i need to select one id from one and other from 2nd table when i clicked on map button it should have to create one temp table will show me two (values)columns what i have mapped just. how can i create temp table? using EF.
table1
SELECT PriceID
FROM Pricing
PriceID
P111
P222
P333
table2
SELECT QueryID
FROM QueryTable
QueryID
Q565UUU
Q661YAA
Q421933
now i'm not getting how can I show them to select one-one Ids from each to map them together any help?
Mapped dataset or any temptable will have two columns
PriceID QueryID
P111 Q565UUU
I'm newbie in MVC3 what should I take to show single columns ?
Please help need to create the UI so that one can easily select SecurityID and CUSIP from each table and map to each other..
what should i use?
Create a new model and populate it with your data and then use that model in your view:
namespace yourProject.ViewModels
{
public class PriceQueryGroup
{
public string PriceID { get; set; }
public string QueryID { get; set; }
}
}
in your view:
#model IEnumerable<yourProject.ViewModels.PriceQueryGroup>
I hope I have understood your question correctly and that this helps.

Allowing for high level of variation in Actions and Views and Models

I am designing a product management system. I am wondering the best way to handle a large amount of variation in each Action/View in my application. The app handles 20 categories and 12 Target Markets, each of which affect the data that needs to be collected for each product. For example, the "QuickAdd" action takes in the core data like Product Name and SKU, plus a few other key pieces of information based on the combo of the Category and Target Market that the product is being added to (examples below). The Category and Target Market are not configurable attributes of the product, the user using the system can only work under a particular combo, for example Toys/USA. The reason for mentioning that is I can't design the form to have sections of attributes for each Category/Market combo, it needs to work like the form is made for just that Category/Market - the user has no knowledge of other combos.
Some examples to hopefully clarify the possible situations:
If I am adding a product to category
Toys with the Target Market USA I need
to ask for the "Age range" and "Did
it pass the safety inspection".
If I am adding a product to category
Toys with Target Market Mexico, I just
need to ask for "Age range".
If I am adding a product to the
category Clothing with the Target
Market USA I need to ask for the
"Style" and "Material"
If I am adding a product to the
category Clothing with the Target
Market Canada I need to ask for the
"Style" and "Material" and "USA Price"
We have 20 categories and 12 Target
Markets, plus there are 10 forms that
need to behave in this fashion, so in
theory there are 2400 distinct
Actions/Views/Models
So the question is, in ASP.NET MVC, what is the best way to handle displaying all these dynamic forms and handling the variations of data that gets sent to the action?
EDIT
A clarification on how the attributes of the product are determined: They are based on the hierarchy of the product belonging to a Category in a Market. For example, it is not the addition of all Toy attributes and USA attributes we'd ask for, it is the attributes of a product that is a Toy sold in the market USA. A Toy sold in the USA needs the "safety inspection" information, but Clothing in the USA does not. A Toy in Mexico also does not need the "safety inspection" information, so that attribute is not inherent to all Toys or all USA products, but rather the fact that is a combo of both Category and Market.
I would create some domain models for the attribute types.
public enum AttributeTypeEnum
{
Currency,
Range,
List,
Number,
Text,
Boolean
}
public interface class IAttribute
{
int Id { get; set; }
string Name { get; set; }
AttributeTypeEnum AttType { get; set; }
}
public abstract class BaseAttribute
{
int Id { get;set;}
string Name { get;set;}
AttributeTypeEnum AttType { get; set; }
}
public class RangeAttribute<T> : BaseAttribute
{
T StartValue { get;set; }
T EndValue { get; set; }
}
Then associate each attribute to one or more Categories
public class CategoryAttribute
{
int Id { get; set; }
IAttribute Attribute { get; set; }
}
You can then have a list of attributes against each category
public class CategoryAttributeService()
{
public IList<CategoryAttributes> GetAttributes(int CategoryId)
{
return new IList<CategoryAttributes>();
}
}
Your controller can then return a list of these attributes in the ViewData.Model.
// controller action
public class CategoryAttributeController : Controller
{
public ActionResult CategoryAttributes(int categoryId)
{
CategoryAttributeService cas = new CategoryAttributeServices();
ViewData.Model = new CategoryAttributeViewData(categoryId)
{
Attributes = cas.GetAttributes(categoryId);
};
return View();
}
}
and let your view handle the type of each item and alter the form controls/display of each item accordingly ie (a range with have a start and end value) a boolean will have a checkbox, Material might be a listbox etc.
you have a number of choices on how to handle the rendering, you could create a separate .ascx control for each attribute type to generate the form controls, or as below create an html helper method
<%# Page Title="" Language="C#" Inherits="ViewPage<CategoryAttributeViewData>" %>
<% foreach(CategoryAttribute attribute in ViewData.Model.Attributes) { %>
<%= Html.RenderAttribute(attribute) %>
<% } %>
and the helper method like
public static string RenderAttribute(this HtmlHelper, ICategoryAttribute att)
{
StringWriter stringWriter = new StringWriter();
using (HtmlTextWriter writer = new HtmlTextWriter(stringWriter))
{
switch(att.AttributeType)
{
case AttributeDataType.Boolean:
CreateCheckBox(writer, att);
break;
case AttributeDataType.List:
CreateListBox(writer, att);
break;
// Other types
}
}
stringWriter.ToString();
}
EDIT: I kind of left Markets out the above so if I understand this correctly, Each Market has a number of categories (one to many) Say USA and Clothing.
The category Clothing can appear in many markets.
Each category has a number of attributes (one to many) (Clothing: color, size) and each attribute can have many Markets (one to Many)
A list of Markets
A list of categories
A list of MarketCategories
A list of CategoryAttributes
A list of Attributes
A list of AttributeMarkets
Markets > MarketCategories > CategoryAttributes > Attributes > AttributeMarkets
Is that correct?
Mac.
For each category's model entity, create a model view that filters properties according to current market,
possibly create a dictionary of unfiltered properties on the fly
or signal the view some other way which properties to ignore/not to ignore.
- If filtering per property is too much, you can use a separate model-view per market (using a dictionary).
You can also use separate views, however that would leave you with loads of views - one dynamic view that loads the correct view-model according to the view (finds the model-view via the controller) and takes the view-model's filters into consideration would be more elegant.
Set up a table in your database that looks something like this:
Category nvarchar(*)
Market nvarchar(*)
AttributeName nvarchar(*)
AttributeType nvarchar(*)
Then store each combination of attributes that you need in that table (obviously, some refactoring can be done, like having an "Attributes" table that stores whether or not an attribute is required for the quick insert and would allow Category/Market combos to share attributes).
Then in your view, read the User's Category and Market combo and dynamically build the form:
In your Page.Load for the form, instantiate the form parts you need, give them meaningful IDs, then in your postback handler, read all the data from the Request.Form object.
A short example:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim stuff As New System.Web.UI.WebControls.TextBox()
stuff.ID = "WOW64"
Page.Form.Controls.Add(stuff)
End Sub
Protected Sub Submit_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim str As String = Request.Form("WOW64")
str = str ' do something with the string'
End Sub
Ended up using a pretty standard implementation of EAV

Resources