I have a MultiSelectList whose dataValueField is a number in a code-behind model and dataTextField field is string.
When I select multiple values in the resulting html select element I get validation error saying that the field must be a number. This makes sense because the backing field is an integer and when you select several entries the id values for buildings are concatenated using commas. What would be the work around for this? Thanks.
The model is as follows.
// Selected buildings are stored in this table.
public class ClientSelectedBuildings
{
public int ClientSelectedBuildingsId { get; set; }
// ...
[Display(Name = "Please select the buildings under consideration.")]
public int? BuildingId { get; set; }
}
// Building list is retrieved from this table.
public class Buildings
{
public int BuildingsId { get; set; }
// ...
[StringLength(255)]
public string BuildingName { get; set; }
}
My view looks as follows.
#model TheApplication.Models.ClientSelectedBuildings
<div class="outer">
<div class="inner">
#Html.LabelFor(t => t.BuildingId)
#Html.ListBoxFor(t => t.BuildingId, (MultiSelectList)ViewBag.Buildings, new { size = "4" })
#Html.ValidationMessageFor(t => t.BuildingId)
</div>
</div>
The issue is that your domain model is only allowing one BuildingId, yet the form will attempt to send multiple via the list box.
This is a perfect example where your domain model does not perfectly match up with the view model. Domain and View each have different concerns and outside of very very basic CRUD situations, you'll find that form views will always require a separate model.
You won't be able to bind directly to ClientSelectedBuildings (without a custom modelbinder). Instead, bind to an intermediate model that then can be mapped into multiple ClientSelectedBuildings.
// Here's the model submitted from the view. This will have to be mapped to domain
// entities.
public class FormModel
{
// ... Other form fields ...
public int[] BuildingIds { get; set;
// ... Other form fields ...
}
// Example controller action that processes the form input.
[HttpPost]
public ActionResult MyPostAction(FormModel input)
{
if (ModelState.IsValid)
{
// Loop all submitted building ids, map the data into domain entities.
foreach(var buildingId in input.BuildingIds)
{
// Create the domain entity.
var selected = new ClientSelectedBuildings
{
ClientSelectedBuildingsId = ... Wherever this comes from ...
BuildingId = buildingId;
};
// Add to the data repository.
this.MyDbContext.ClientSelectedBuildings.Add(selected);
}
// Submit all changes to the data context.
this.MyDbContext.ClientSelectedBuildings.SaveChanges();
// ... Return redirect to success view ...
}
// ... Return error view ...
}
Related
I'm using Entity Framework Database First approach. Let's say I have a model class called Product and that class has a NumberOfViews property. In the Edit page I pass an instance of the product class to the controller.
The problem is I can't add #Html.EditorFor(model => model.NumberOfViews) in the Edit page, because it's supposed that NumberOfViews is updated with every visit to the product page, and NOT by the website Admin.
And I can't add it as #Html.HiddenFor(model => model.NumberOfViews), because if the Admin Inspected the element, he can edit it manually.
Also If I try to programmatically set the value on the server-side (e.g., Product.NumberOfViews = db.Products.Find(Product.Id).NumberOfViews;), I get the following error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
And if I don't add it to either the view or the controller, the value will be null, thus overriding any previous value.
So what should I do?
I have noticed a lot of people use the same model for their Entity Framework as they do for their MVC Controller. I generally discourage this practice. In my opinion, a database model is not the same as a view model.
Sometimes a view needs less information than what the database model is supplying. For example while modifying account password, view does not need first name, last name, or email address even though they may all reside in the same table.
Sometimes it needs information from more than one database table. For example if a user can store unlimited number of telephone numbers for their profile, then user information will be in user table and then contact information with be in contact table. However when modifying user profile, they may want to add/edit/delete one or more of their numbers, so the view needs all of the numbers along with first name, last name and email address.
This is what I would do in your case:
// This is your Entity Framework Model class
[Table("Product")]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
public string Name { get; set; }
public int NumberOfPageViews { get; set; }
}
// This is the model you will use in your Edit action.
public class EditProductViewModel
{
public int ProductId { get; set; }
public string Name { get; set; }
}
public class ProductController : Controller
{
IProductService service;
//...
[HttpGet]
public ActionResult Edit(int productId)
{
var product = service.GetProduct(productId);
var model = new EditProductViewModel()
{
ProductId = product.ProductId,
Name = product.Name
};
return View(model);
}
[HttpPost]
public ActionResult Edit(EditProductViewModel model)
{
if (ModelState.IsValid)
{
var product = service.GetProduct(model.ProductId);
product.Name = model.Name;
service.Update(product);
}
// ...
}
}
I have a Asp.net MVC grid.
My problem is I need to display multiple columns in a single row.
For example:
Name Date Compensation
Id USD - 99999
Grade INR - 99999
The above layout is a single row in the grid.
All the columns (Name, Id, Grade, Curency1, Amount1, Currency2, Amount2 ) are available in a single record as separate columns. Here Currency1 means USD and Currency2 means INR.
Any ideas how to do this. I am using a strongly typed model and EF6.
I think the best way to do this would be to create a separate 'type' and model for each multi-faceted column, then try to display this type in the webgrid (which I show is possible in the latter part of my example).
For example:
Create a new 'type' (or 'column') class called CompensationColumn:
...
using System.Web.Mvc;
namespace yourproject.Columns // I put this in its own namespace/folder - you don't have to
{
public class CompensationColumn
{
public string Currency1 { get; set; }
public int Amount1 { get; set; }
public string Currency2 { get; set; }
public int Amount2 { get; set; }
public CompensationColumn(string currency_1, int amount_1, string currency_2, int amount_2)
{
Currency1 = currency_1;
Amount1 = amount_1;
Currency2 = currency_2;
Amount2 = amount_2;
}
}
}
Then create a file called CompensationColumn.cshtml in the yourproject/Shared/EditorTemplates folder (if the Shared folder doesn't exist you can also create a your view/DisplayTemplates folder). Define how this column will look, as if it was a custom 'type' (modify this to your liking):
#model yourproject.Columns.CompensationColumn
#if (Model != null)
{
#Model.Currency1<text> - </text>#Model.Amount1<text><p/></text>
#Model.Currency2<text> - </text>#Model.Amount2
}
else
{
}
Then in your Models folder, create a partial class to extend your current EF table model (file name shouldn't matter). I am going to assume your table is 'employee_table'. I am also adding Metadata for the model in this class as it is a good place to put it if you are using a database-first design:
using System.Web.Mvc;
using yourproject.Columns;
namespace yourproject.Models
{
[MetadataType(typeof(EmployeeModelMetaData))] // This links the metadata class below
public partial class employee_table // This should be the EF class name
{
[DisplayName("Compensation")]
public CompensationColumn Compensation { get; set; } // Here we add a new field for your row
}
public class EmployeeModelMetaData
{
// copy your EF class fields here and decorate them with dataannotations. This is helpful
// if you are using a database-first design as it won't get overwritten when db changes.
[DisplayName("Id")]
public int emp_id { get; set; }
[DisplayName("Amount")]
[DisplayFormat(DataFormatString = "{0:c}", ApplyFormatInEditMode = true)]
public int emp_amount1 { get; set; }
// etc . . .
}
}
I make a few assumptions here about a database-first design, but you should be able to figure out how to adapt it to a code-first design if needed.
If you also need to edit elements this column type together, then you would need to create a model binder, but I'm not going there since you only mentioned displaying it.
To get the display template to display in the webgrid, you will need to format: the columns of the webgrid. In your view with an IEnumerable model (e.g. your Index view):
#{
var grid = new WebGrid(Model);
List<WebGridColumn> columns = new List<WebGridColumn>();
WebGridColumn col = grid.Column(columnName: "Col3", header: "Compensation", format: (item) =>
{
yourproject.Columns.CompensationColumn c = item.Compensation; return Html.DisplayFor(model => c);
} );
columns.Add(col);
}
#grid.GetHtml(columns: columns)
This last snippet I adapted from Frédéric Blondel's code here
I have a few entities that I want to fill into a few dropdown lists on a single form. Which is the best way to go about doing so. For multiple models in a single view I've created a viewmodel and threw the entities into it but how can I bring back the list in the database say for entity "Network" and fill the dropdown with "Name" and "NetworkID"?
First create the Model:
public class Data
{
public List<tbl_Dept> lstDepatrment;
public List<tbl_employees> lstEmployee;
//other
}
Then just Create a View
#model MVCApp.Models.Data
#{
var categoryList = Model.lstDepatrment.Select(cl => new SelectListItem
{
Value = cl.Dept_ID.ToString(),
Text = cl.Dept_Description == null ? String.Empty : cl.Dept_Description
});
//list for other Drop Down
}
#(Html.DropDownList("sampleDropdown", categoryList, "-----Select-----"))
You can do as follows:
Designing your model:
Prepare Select List for as many dropdowns you want
For eg:
Public class ModelName
{
...// Properties
public IEnumerable<SelectListItem> ListName1 { get; set; }
public IEnumerable<SelectListItem> ListName2 { get; set; }
public IEnumerable<SelectListItem> NetWorkList { get; set; }
... //etc
}
Prepare and bind List to Model in Controller :
public ActionResult Index(ModelName model)
{
var networks = // Your network List
model.NetWorkList = networks.Select(x=> new SelectListItem() {
Text = x.Name,
Value = x.NetworkID
});
..// Same as above prepare the list for other dropdowns
return View(model);
}
Then in your view prepare your dropdown as follows:
#Html.DropDownListFor(m => Model.NetworkID,Model.NetWorkList)
Well in that case you can keep all the model list data in somewhere in java script model and then using the JQuery you can bind all of Dropdown controls with same model list.
Alternatively you can fetch that data using Ajax and bind those Dropdowns there in java script and retrieve the value rather then throwing data multiple list from controller.
I am using C#, MVC3, EF5, SQL Server 2008 R2.
I have an intersection table ie
Lecturer -< LecturerCourse >- Course
The list of Lecturers are populated.
When I add a course, it would be neat to have a list of Lecturers that I could select from, that teach the course in question. When I save the new Course record, this multiselect also should save its data back to the "LecturerCourse" table via Model Binding.
I am using EF5.
Can you recommended a simple and standard approach to solving CRUD for a join, ie "LecturerCourse", table? I have looked online, but some of the approaches seem very complicated.
Many thanks.
Alright, it's going to be a long one. To allow this to happen in "one page" (through POST, or you could use Ajax, technically), you need a combination of a Get and Post version of the method and to construct your view model correctly. Below are the classes that I will use for demonstration purposes:
public class NewCourse
{
[Required]
public string Name { get; set; }
// And your other properties
public int[] LecturerIds { get; set; }
}
public class ViewLecturer
{
public int Id { get; set; }
public int Name { get; set; }
}
public class NewCourseViewModel
{
public NewCourse Course { get; set; }
public IEnumerable<ViewLecturer> Lecturers { get; set; }
}
NewCourseViewModel will be the model for the View (see below). ViewLecturer will give you a lighter mapping between your available Lecturer and the information required to Add to them.
As for the Controller:
public class CourseController : Controller, IDisposable
{
private Lazy<YourContext> lazyContext =
new Lazy<YourContext>(() => new YourContext());
private YourContext Context
{
get { return lazyContext.Value; }
}
public ActionResult New()
{
var model = new NewCourseViewModel {
Course = new NewCourse(),
Lecturers = Context.Lecturers
.Select(l => new ViewLecturer { Id = l.Id, Name = l.Name })
};
return View(model);
}
[HttpPost]
public ActionResult New(NewCourse course)
{
if(ModelState.IsValid)
{
var lecturers = course.Lecturers
.Select(l => new Lecturer { Id = l.Id })
.ToList();
foreach(var lecturer in lecturers)
Context.Lecturers.Attach(lecturer);
var newCourse = new Course {
Name = course.Name,
// ... and the rest of the mapping
Lecturer = lecturers
};
context.Courses.Add(newCourse);
context.SaveChanges();
// Could have to handle DbUpdateException if you want
return RedirectToAction(...);
}
return View(new NewCourseViewModel {
Course = course,
Lecturers = Context.Lecturers
.Select(l => new ViewLecturer { Id = l.Id, Name = l.Name })
});
}
public void Dispose()
{
if(lazyContext.IsValueCreated)
lazyContext.Value.Dispose();
}
}
Your first New method will give you the entry point for your Course creation page. The rest of the validation and actual adding will be done through the [HttpPost]overload. As for your View (that should be in the ~/Views/Course/New.cshtml):
#model NewCourseViewModel
// ... Then when you are ready to begin the form
#using(Html.BeginForm("New", "Course", FormMethod.Post))
{
// Your List of Lecturers
#Html.ListBoxFor(m => m.Course.LecturerIds,
new MultiSelectList(
Model.Lecturers,
"Id",
"Name",
m.Course.LecturerIds ?? new int[0]
))
// Your Other Model binding
}
When the submit button will be pressed, the action matched will be the New(NewCourse course). The names are important because of the way the HtmlHelpers generate their Ids. Because we are only included one property of the whole view model, it will match the parameter name course based on the view model's Course property. You will get a list of Ids for the Lecturers which you will be able to use to attach to the DbContext and add directly to the new Course model (Entity Framework will do the rest). In cases where there was a problem, we can get back the list of lecturers and re-use the same NewCourse in the view model.
Now this is example is very basic but it should give you a good starting point as to how you can structure your view model.
I have an application that shows a grid/table of questions and each question has a status dropdown. There are around 1-200 questions and each status drop down has about 50 choices that are the same for every row of the grid.
My controller passes the following model to a view:
IEnumerable<Question.Grid>
Then in my view I have the following code that prints out the detail lines of a grid table:
<tbody class="grid">
#if (Model != null) {
foreach (var item in Model) {
#Html.DisplayFor(model => item, "QuestionDetail")
}
}
</tbody>
Each of the grid lines has a status dropdown and I would like to pass the data for the dropdown (same for every row) to the QuestionDetail view. What's the best way for me to send this additional information so that in my view I can have something like the following:
#Html.DropDownList("Question.Status", Status, new { id = "StatusID"})
First of all, don't pass IENumerable of view models. Rather define one that has IEnumerable as property like this:
public class EnumViewModel
{
public IEnumerable<Question.Grid> Questions { get; set; }
public int MyAdditionalFieldIWantedToPassAlong { get; set; }
}
public class Question.Grid
{
public string MyExistingFields { get; set; }
// as many as you had
public string MyAdditionalFields { get; set; }
// as much as you want
}
and have your view receive one model instead of list of models as #model EnumViewModel.
At this point it must be very easy for you to add any additional information as your heart pleases inside the EnumViewModel should this information belong there. If its more specific to Questions,put it there and enjoy your items inside every model in the list containing that StatusID.
Think of it as just a container for you to hold data your view needs to
display and
post back to controller
Hope this helps.