问题
I am Working on ASP.NET MVC 5, EF 6, Razor Engine, VB Language and Database First Approach with VS 2013.
Now, in my DB; I have two tables as below:
CREATE TABLE [dbo].[Group]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY(1, 1),
[Name] VARCHAR(50) NOT NULL
)
and
CREATE TABLE [dbo].[Subscriber]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY(1, 1),
[FirstName] [nvarchar](100) NOT NULL,
[MiddleName] [nvarchar](100) NULL,
[LastName] [nvarchar](100) NOT NULL,
[Email] [varchar] (200) NOT NULL UNIQUE,
[GroupId] INT NULL REFERENCES [Group] ON DELETE SET NULL
)
Now, when I autogenerate the Controllers and Views using Scaffolding; I get a <select> control (with all Group
items as <option> inside) in "Create Subscriber" and "Edit Subscriber" views.
But actually, I want the "Create Group" and "Edit Group" views to ask me the Subscribers
I want to add in the particular group. The HTML control for the same can be a list of checkbox or <select multiple="multiple"> with all Subscriber
items as <option>s.
How can I autgenerate/implement that?
回答1:
Don't rely too heavily on the scaffolding. The whole point is that it gives you a base to work from; it's not the be-all-end-all to your view. You can and should modify the scaffolding to suit your needs, and honestly, more often than not, it's easier just to start from scratch than to try to undo all the unnecessary fluff the scaffolding adds.
That said, especially when choosing multiple related items at once, you need a view model. Trying to use your entity for this is going to run out of steam fast. So create a class like:
public class GroupViewModel
{
// `Group` properties you need to edit here
public List<int> SelectedSubscriberIds { get; set; }
public IEnumerable<SelectListItem> SubscriberChoices { get; set; }
}
Then, in your controller:
// We'll use this code multiple times so it's factored out into it's own method
private void PopulateSubscriberChoices(GroupViewModel model)
{
model.SubscriberChoices = db.Subscribers.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = m.FirstName + " " + m.LastName
});
}
public ActionResult Create()
{
var model = new GroupViewModel();
PopulateSubscriberChoices(model);
return View(model);
}
[HttpPost]
public ActionResult Create(GroupViewModel model)
{
if (ModelState.IsValid)
{
// Map the posted values onto a new `Group` instance. To set `Subscribers`,
// lookup instances from the database using the list of ids the user chose
var group = new Group
{
Name = model.Name,
Subscribers = db.Subscribers.Where(m => model.SelectedSubscriberIds.Contains(m.Id))
};
db.Groups.Add(group);
db.SaveChanges()
return RedirectToAction("Index");
}
PopulateSubscriberChoices(model);
return View(model);
}
public ActionResult Edit(int id)
{
var group = db.Groups.Find(id);
if (group == null)
{
return new HttpNotFoundResult();
}
// Map `Group` properties to your view model
var model = new GroupViewModel
{
Name = group.Name,
SelectedSubscriberIds = group.Subscribers.Select(m => m.Id).ToList()
};
PopulateSubscriberChoices(model);
return View(model);
}
[HttpPost]
public ActionResult Edit(int id, GroupViewModel model)
{
var group = db.Groups.Find(id);
if (group == null)
{
return new HttpNotFoundResult();
}
if (ModelState.IsValid)
{
group.Name = model.Name;
// Little bit trickier here
// First remove subscribers that are no longer selected
group.Subscribers.Where(m => !model.SelectedSubscriberIds.Contains(m.Id))
.ToList().ForEach(m => group.Subscribers.Remove(m));
// Now add newly selected subscribers
var existingSubscriberIds = group.Subscribers.Select(m => m.Id);
var newSubscriberIds = model.SelectedSubscriberIds.Except(existingSubscriberIds);
db.Subscribers.Where(m => newSubscriberIds.Contains(m.Id))
.ToList().ForEach(m => group.Subscribers.Add(m));
db.Entry(group).State = EntityState.Modified;
db.SaveChanges()
return RedirectToAction("Index");
}
PopulateSubscriberChoices(model);
return View(model);
}
The edit post action is the most difficult. In order to not get errors about duplicate keys and such, you need to make sure you don't add duplicate items to the collection. You also need to make sure to remove the relationship between this group and any items the user has unselected. Other than that, it's pretty straight forward.
Finally in your views, you just need a to render the select list:
@model Namespace.To.GroupViewModel
...
@Html.ListBoxFor(m => m.SelectedSubscriberIds, Model.SubscriberChoices)
UPDATE
Adding converted VB code. This may not work 100% out of the box. Anyone with more VB experience may feel free to edit this to correct any issues.
View Model
Public Class GroupViewModel
' Group properties you need to edit here
Public Property SelectedSubscriberIds As List(Of Integer)
Public Property SubscriberChoices As IEnumerable(Of SelectListItem)
End Class
Controller Code
' We'll use this code multiple times so it's factored out into it's own method
Private Sub PopulateSubscriberChoices(model As GroupViewModel)
model.SubscriberChoices = db.Subscribers.[Select](Function(m) New SelectListItem With { _
.Value = m.Id, _
.Text = m.FirstName & " " & m.LastName _
})
End Sub
Public Function Create() As ActionResult
Dim model as New GroupViewModel
PopulateSubscriberChoices(model)
Return View(model)
End Function
<HttpPost> _
Public Function Create(model As GroupViewModel) As ActionResult
If ModelState.IsValid Then
' Map the posted values onto a new `Group` instance. To set `Subscribers`,
' lookup instances from the database using the list of ids the user chose
Dim group = New Group With { _
.Name = model.Name, _
.Subscribers = db.Subscribers.Where(Function(m) model.SelectedSubscriberIds.Contains(m.Id)) _
}
db.Groups.Add(group)
db.SaveChanges()
Return RedirectToAction("Index")
End If
PopulateSubscriberChoices(model)
Return View(model)
End Function
Public Function Edit(id As Integer) As ActionResult
Dim group = db.Groups.Find(id)
If group Is Nothing Then
Return New HttpNotFoundResult()
End If
' Map `Group` properties to your view model
Dim model = New GroupViewModel With { _
.Name = group.Name, _
.SelectedSubscriberIds = group.Subscribers.[Select](Function(m) m.Id).ToList _
}
PopulateSubscriberChoices(model)
Return View(model)
End Function
<HttpPost> _
Public Function Edit(id As Integer, model As GroupViewModel) As ActionResult
Dim group = db.Groups.Find(id)
If group Is Nothing Then
Return New HttpNotFoundResult()
End If
If ModelState.IsValid Then
group.Name = model.Name
' Little bit trickier here
' First remove subscribers that are no longer selected
group.Subscribers.Where(Function(m) Not model.SelectedSubscriberIds.Contains(m.Id)).ToList().ForEach(Function(m) group.Subscribers.Remove(m))
' Now add newly selected subscribers
Dim existingSubscriberIds = group.Subscribers.[Select](Function(m) m.Id)
Dim newSubscriberIds = model.SelectedSubscriberIds.Except(existingSubscriberIds)
db.Subscribers.Where(Function(m) newSubscriberIds.Contains(m.Id)).ToList().ForEach(Function(m) group.Subscribers.Add(m))
db.Entry(group).State = EntityState.Modified
db.SaveChanges()
Return RedirectToAction("Index")
End If
PopulateSubscriberChoices(model)
Return View(model)
End Function
来源:https://stackoverflow.com/questions/27381766/asp-net-mvc-5-scaffolding-for-many-to-one-relationship