MVC, UpdateModel OR delete depending on form field value? - asp.net-mvc

I'm very new to this, so any help is appreciated.
I'll use the Dinners/RSVPs relationship for detailing my problem. One Dinner has many RSVPs.
On my Dinner edit page/view I want to be able to edit the Dinner information AND the RSVPs information.
I have that part working, based on the answer given here by James S:
int i = 0;
foreach (var r in Dinner.RSVPs) {
UpdateModel(r, "Dinner.RSVPs[" + i++ + "]");
}
But what if I want to Delete an RSVP based on the user clicking a checkbox next to the RSVP on the edit view? Something like this on the edit view:
<% int i = 0;
foreach (var rsvp in Model.RSVPs){%>
<%=Html.CheckBox("RemoveRSVP[" + i + "]") %>
<%= Html.TextBox("Dinner.RSVPs[" + i + "].Name", rsvp.Name) %>
<% i++;
}%>
I tried this, but it's not working, obviously:
Dinner dinner = dinnerRepository.GetDinner(id);
int i = 0;
foreach (var r in dinner.RSVPs) {
if (Boolean.Equals(RemoveRSVP[i], true){
dinner.RSVPs.Remove(r);
else
UpdateModel(r, "Dinner.RSVPs[" + i+ + "]");
i++;
}
I can't delete/remove an RSVP using UpdateModel can I?
Let me know if anything isn't clear.
Thanks.

I tried this, but it's not working, obviously:
Is your difficulty in actually making the delete go through? Or is it in processing the form to detect which ones should be deleted? e.g. which line doesn't work:
dinner.RSVPs.Remove(r);
or
if (Boolean.Equals(RemoveRSVP[i], true)
?
For #1
If your repository is backed by Linq 2 Sql and RSVP is an entity, you will usually have to cause DeleteOnSubmit() to be called in order for the record to be deleted from the database--just calling Remove() on the association will not be enough. You probably will add one of the following to your DinnerRepository to do this:
DinnerRepository.DeleteRsvp(RSVP item)
DinnerRepository.DeleteRsvp(Dinner dinner, RSVP rsvp)
Alternately, if you want LINQ to perform the delete automatically, you can edit the DBML as XML (right click, open with, XML Editor) and add the following attribute to the entity association:
<Association Name="..." ... DeleteOnNull="true" />
For #2
I usually construct this type of "repeating entity-delete checkbox" form so that the posted values are a list of the entity IDs I want to delete. To facilitate this I use an alternate CheckBox helper:
public static class HtmlExtensions
{
/// <summary>
/// Alternate CheckBox helper allowing direct specification of "name", "value" and "checked" attributes.
/// </summary>
public static string CheckBox(this HtmlHelper html, string name, string value, bool isChecked)
{
string tag = String.Format("<input type=\"checkbox\" name=\"{0}\" value=\"{1}\" {2}/>",
html.AttributeEncode(name),
html.AttributeEncode(value),
isChecked ? "checked=\"checked\" " : ""
);
return tag;
}
}
and create the checkboxes like so:
<%= Html.CheckBox("RemoveRsvpIds", rsvp.RsvpId.ToString(), false) %>
and consume the list like so:
public ActionResult TheFormInQuestion(int dinnerId, int[] removeRsvpIds)
{
var dinner = DinnerRepository.GetDinner(dinnerId);
foreach (var rsvp in dinner.RSVPs)
{
if (removeRsvpIds.Contains(rsvp.RsvpId))
{
// Perform Delete
}
else
{
// Perform Update
}
}
// The rest
}
I can't delete/remove an RSVP using UpdateModel can I?
The purpose of UpdateModel() is to automagically copy property values from the posted form onto an already-existing object--not to create new or destroy existing entities. So no, not really. It wouldn't be the expected behavior.

I am not totally familiar with the NerdDinner code but don't they use Linq to SQL for their backend? If that is the case I would think you could tackle this in a traditional web approach and append the record ID to the value of each check box in a list of items to be deleted. Then you could catch the collection of IDs on the server side and do a DeleteAllOnSubmit by passing a selection query of entities to the delete call? Let me know if you need more detail.

Related

MVC many to many delete

I have many to many relationship some tables. I am using entity framework.
Reservations >> ApartsToReservations << Aparts
ApartsToReservations include ReservationID and ApartID.
and my delete action below:
public ActionResult DeleteReservation(string resultId)
{
var result = Convert.ToInt32(resultId.Trim());
//just I have reservationID
return View();
}
How can delete reservation?
How can delete reservation?
For example:
[HttpPost] // <- should be a POST action because it's modifying data
public ActionResult DeleteReservation(string resultId)
{
var result = Convert.ToInt32(resultId.Trim());
//just I have reservationID
using (var context = new MyContext())
{
var reservation = new Reservation { ReservationID = result };
context.Reservations.Attach(reservation);
context.Reservations.Remove(reservation);
context.SaveChanges();
}
// ... redirect to Index or something, for example:
// return RedirectToAction("Index");
}
If you already have a context instance as a member of your controller class use this instead of creating a new one in the using block.
The code will also delete the related records in the ApartsToReservations link table because by default the relationships to this table are configured with cascading delete.
I'm not quite sure if this is what you are looking for because it actually doesn't matter if Reservation is involved in the many-to-many relationship you mentioned. It is just the (or one) standard way to delete an entity with DbContext.
If you are looking for something different please try to clarify your question or ask a new one which is clearer.

missunderstanding mvc default binding

I have multiselect jquery plagin (Choosen) and when I use it in 'Multiple Select' mode I expect in controller next values:
posted string = 'value1,value2...'
really have
posted string = 'value2'
only if I reffer directly to FormCollection I'll get expected values as below:
[HttpPost]
public ActionResult TagSearech(/*string tagSelect*/FormCollection c)
{
// only one value here
// string[] names = tagSelect.Split(',');
// as expected: value1,....
string expectedValue = c['tagSelect'];
return View();
}
I cant understand what might cause this behavior.
EDIT
Here is View:
#using (Html.BeginForm("TagSearech", "Tag"))
{
#Html.DropDownList("tagSelect", Model, new { #class = "chzn-select", data_placeholder = "tag names", multiple = "" })
<input type="submit"/>
}
MVC will attempt to bind the input data on the URL into the model. I haven't seen how Chosen.js posts the data back to the server, but essentially its coming in in the wrong format, so MVC binds the first element it sees to the string Model.
The FormsCollection retrieves all of the data that was posted in the URL, which is why all of your selected values can be seen there.
Did you try changing the incoming model from string to string[], and see if all of the items are bound to the array?

ASP.NET / MVC HTML ActionLink - how to pass returned Javascript value to ActionLink routeValue parm?

(target framework is 4.0)
I am implementing a feature to delete multiple records in a simple table. In order to do this, I am using simple HTML checkboxes with a Javascript function to loop through each one to find which are selected. The Javascript is returning an array of int values representing the record IDs to delete.
I am using an ActionLink to call all of this, and to ultimately return to the Delete function (with an int array as a parm) in my controller file to actually submit the deletions to the database.
My problem is, I don't know how to take the array I get back from the Javascript to place into the ActionLink, to pass it into the controller.
Here is the code with which I am working:
<p>
#Html.ActionLink("Add New", "Add") | #Html.ActionLink("Delete Selected", "Delete", new { id = "???" }, new { onclick = "return getProjectsToDelete()" })
</p>
<script type="text/javascript">
function getProjectsToDelete() {
var answer = confirm("Are you sure you wish to delete the selected projects?");
if (answer) {
var listToDelete = new Array();
var arrayCount = 0;
for (i = 0; i < document.projectList.deleteProject.length; i++) {
if (document.projectList.deleteProject[i].checked) {
listToDelete[arrayCount] = document.projectList.deleteProject[i].value;
arrayCount++;
}
}
return listToDelete;
}
}
</script>
If I understand you correctly, you want to know how to push back a collection of IDs to a controller?
In general, this would be done as a FORM and POST the form to the back end. I myself prefer to do this via AJAX but thats just me.
While you can do this using a GET and stuffing all of the IDs in the query string, its dirty!
It would look something like /controller/action?id=7&id=8&id=9
The Model Binder in .NET will convert that in to a List<int> or int[].
I would suggest using ".push" in Javascript to manage you array of IDs. You can also use the ".join" method on your array to create a clean querystring.

How do you persist querystring values in asp.net mvc?

What is a good way to persist querystring values in asp.net mvc?
If I have a url:
/questions?page=2&sort=newest&items=50&showcomments=1&search=abcd
On paging links I want to keep those querystring values in all the links so they persist when the user clicks on the "next page" for example (in this case the page value would change, but the rest would stay the same)
I can think of 2 ways to do this:
Request.Querystring in the View and add the values to the links
Pass each querystring value from the Controller back into the View using ViewData
Is one better than the other? Are those the only options or is there a better way to do this?
i use a extension method for that:
public static string RouteLinkWithExtraValues(
this HtmlHelper htmlHelper,
string name,
object values)
{
var routeValues = new RouteValueDictionary(htmlHelper.ViewContext.RouteData.Values);
var extraValues = new RouteValueDictionary(values);
foreach (var val in extraValues)
{
if (!routeValues.ContainsKey(val.Key))
routeValues.Add(val.Key, val.Value);
else
routeValues[val.Key] = val.Value;
}
foreach (string key in htmlHelper.ViewContext.HttpContext.Request.Form)
{
routeValues[key] = htmlHelper.ViewContext.HttpContext.Request.Form[key];
}
foreach (string key in htmlHelper.ViewContext.HttpContext.Request.QueryString)
{
if (!routeValues.ContainsKey(key) && htmlHelper.ViewContext.HttpContext.Request.QueryString[key] != "")
routeValues[key] = htmlHelper.ViewContext.HttpContext.Request.QueryString[key];
}
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
return string.Format("{1}", urlHelper.RouteUrl(routeValues), name);
}
I would process the QueryString in the view (your option #1), instead of passing it in from the controller. This approach makes the view more self-contained, allowing you to convert it into a view control and re-use it across different views.
Note: Accessing the QueryString directly in the view may seem like a violation of the design principle of separating the Model and View, but in reality this data is a navigational concern which is related to the view, not really part of the model.
I would just keep the values in the Session that way the paging links only need to have;
/questions?page=2
/questions?page=3
The one reason why I would not us the QueryString is because I don't want the user to see the values that I am passing to the program. It makes it way too easy for them to go into the address bar and start changing the values to 'see what happens'. With this code all they could do is change the page number.
Here's how I done it in Asp.Net Core, first assign the query string parameters to ViewBags in your controller:
[HttpGet("/[controller]/[action]/{categoryId?}/{contractTypeId?}/{locationId?}")]
public IActionResult Index(Guid categoryId, int contractTypeId, Guid locationId)
{
ViewBag.CategoryId = categoryId;
ViewBag.ContractTypeId = contractTypeId;
ViewBag.LocationId = locationId;
...
}
Then pass the values to your links like so:
<a asp-action="Index" asp-controller="Jobs"
asp-route-categoryId="#teachingCategory.Id"
asp-route-contractTypeId="#ViewBag.ContractTypeId"
asp-route-locationId="#ViewBag.LocationId">
#teachingCategory.Description (#teachingCategory.Rank)
</a>
<a asp-action="Index" asp-controller="Jobs"
asp-route-categoryId="#ViewBag.CategoryId"
asp-route-contractTypeId="#typeOfEmployment.Id"
asp-route-locationId="#ViewBag.LocationId">
#typeOfEmployment.Name
</a>
<a asp-action="Index" asp-controller="Jobs"
asp-route-categoryId="#ViewBag.CategoryId"
asp-route-contractTypeId="#ViewBag.ContractTypeId"
asp-route-locationId="#item.Id">
#item.Id
</a>
Note that every link keep its own actual value and pass the rest of the route values through what we passed to ViewBag.

ASP.NET MVC: How do I keep a field byte[]

I've got a field which its type is byte[]. This field will hold my entity's RecordVersion property (timestamp in the database). How do I keep this field so that when I save my entity it is available?
I've tried two different things and haven't succeeded so far:
This renders "System.Byte[]":
<%= Html.Hidden("RecordVersion", Model.RecordVersion.ToString()) %>
This throws a ModelStateError where the type couldn't be converted:
ViewData["RecordVersion"] = entity.RecordVersion
Apparently the default MVC's mechanism that does the bind/unbind doesn't like much byte[] fields .....
You need to make a modelbinder and register it.
This article shows how to use a timestamp from a linq database in a hidden field much like what you are doing.
ModelBinders.Binders.Add(typeof(Binary), new LinqBinaryModelBinder());
In global.asax to register it.
That LinqBinaryModelBinder is in the futures assembly. If you want to user byte[] you'll have to write one yourself.
Have you tried.
<%= Html.Hidden("RecordVersion", System.Text.Encoding.Unicode.GetString(Model.RecordVersion)) %>
I wouldn't put the timestamp on the form. If you want to keep the object around I'd cache it server side and retrieve it from the cache using the id. Otherwise, you can re-retrieve the object from the database and apply the changes from your form data. The latter is what I do, using TryUpdateModel.
public ActionResult Update( int id )
{
using (var context = new DataContext())
{
var model = context.Models.Where( m => m.ID == id ).Single();
if (TryUpdateModel( model ))
{
...
context.SubmitChanges(); // wrapped in try/catch
...
}
else
{
...
}
}
return RedirectToAction( "Show", new { id = id } );
}

Resources