using the same partial view with different buttons - asp.net-mvc

I have the following partial view, which lists users in a table. Each row has an Enroll button, which enrolls the user to the selected course.
I need almost the same view for another task. However, I need to add users to Discussions (instead of enrolling them to a course). I know I can create another view and change the Enroll buttons to Add buttons.
However, I wonder if there is a more effective way of doing this. My approach does not seem to be easy to maintain.
#model IEnumerable<ApplicationUser>
<h4>Search results:</h4>
<table class="table-condensed" style="font-size:smaller">
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td>
<input class="btn_Enroll" data-userid="#item.Id" value="Enroll" type="button" />
</td>
</tr>
}
</table>
<script>
$(".btn_Enroll").click(function () {
//code for enrolling
var course_id = $("#hdn_SelectedCourseId").val();
})
</script>

One way to do it is by setting an attribute of your calling action method that will render this view
<table class="table-condensed" style="font-size:smaller" data-module="#ViewData["module"]"></table>
and then use it in your JS code
<script>
$(".btn_Enroll").click(function () {
//code for enrolling
var course_id = $("#hdn_SelectedCourseId").val();
var moduleType = $(this).closest("table").attr("data-module");
if (moduleType === "enroll")
{
//Do enrollment
}
else if (moduleType === "discussion")
{
//discuss here
}
})
For example on home page you have links like
#Html.ActionLink("enrollment", "Index", new { module = "enroll" })
#Html.ActionLink("Discussion", "Index", new { module = "discussion" })
and your ApplicationUserController has index action like this
public ActionResult Index(string module)
{
ViewData["module"] = module;
return View();
}
However if scope of project or requirements can change for enrollment and/or discussion then better to keep then separate to avoid complexity of code in single page as well as in single JS code.

Related

.NET MVC: Removing items from List in model

So here's my problem. I've been trying to solve this for weeks and nothing, so I'm biting the bullet and asking for help.
Basically, I'm trying to write a computer encyclopedia-cum-inventory management-cum-auditing program. Seeing that MVC is all the rage these days I decided to step out of my comfort zone of classic .NET and try MVC.
I have a model with partially the following fields:
public class SoundCard
{
public Guid Id { get; set; }
...
public virtual List<SoundChipset>? SoundChipsets { get; set; }
...
public virtual List<MidiSynthChipset>? MidiSynthChipsets { get; set; }
...
}
The model is scaffolded into creating a controller and then a set of view pages. The add view works brilliantly and I could add sound and midi chipsets as needed. The edit view is where my problem lies: I could add new sound chipsets and midi chipsets, but could not remove the added ones.
The partial code for the controller for edit is as follows:
public async Task<IActionResult> Edit(Guid id, [Bind("Id,ModelSeries,ModelName,ModelNumber,ReleaseDate,HasCDROMInterface,HasSCSIPort,HasIDEPort,HasNECCDPort,HasMatsushitaCDPort,HasWaveBlasterPort,HasRAMForWaveTable,RAMSizeKB,HasGamePort,HasMPU401Port,numAudioOutPorts,numAudioInPorts,numAudioBiDirectionalPorts,numCoaxInPorts,numCoaxOutPorts,numOpticalInPorts,numOpticalOutPorts,numMidiInPorts,numMidiOutPorts")] SoundCard soundCard)
{
...
string[] selectedSndChipsets = Request.Form["lbSoundChipsetsSelected"].ToArray();
List<SoundChipset> sndChipset = new List<SoundChipset>();
foreach (string uuid in selectedSndChipsets)
{
sndChipset.Add(Factories.SoundChipsetDDLFactory.getSoundChipsetByUUID(_context, uuid));
}
soundCard.SoundChipsets = sndChipset;
string[] selectedMidChipsets = Request.Form["lbMidiChipsetsSelected"].ToArray();
List<MidiSynthChipset> MidChipsets = new List<MidiSynthChipset>();
foreach (string uuid in selectedMidChipsets)
{
MidChipsets.Add(Factories.MidiSynthChipsetDDLFactory.getMidiSynthChipsetByUUID(_context, uuid));
}
soundCard.MidiSynthChipsets = MidChipsets;
_context.Update(soundCard);
await _context.SaveChangesAsync();
...
So, practically recreating the Sound Chipsets and Midi Chipsets lists from scratch every single time. Problem is, the program treats the list as new objects to add to the existing list, it does not erase the current list despite the list being a new one!
I've tried to apply a Clear() command to the list but instead the program tossed an NullReferenceException which is puzzling because the list is supposed to be populated.
For completeness sake, here's part of the code for the edit frontend. It's partially JS to handle moving items between two boxes:
<label asp-for="SoundChipsets" class="control-label"></label>
<table>
<tr>
<th>Available</th>
<th>↔</th>
<th>Selected</th>
</tr>
<tr>
<td>
#Html.ListBox("lbAllSoundChipsets",(IEnumerable<SelectListItem>)ViewBag.SoundChipsets, new {#id="lbAllSoundChipsets", #style="min-width: 250px;"})
</td>
<td>
<input onclick="Javascript:SwitchListBoxItems('lbAllSoundChipsets', 'lbSoundChipsetsSelected');" type="button" value="→" /><br />
<input onclick="Javascript:SwitchListBoxItems('lbSoundChipsetsSelected', 'lbAllSoundChipsets');" type="button" value="←" />
</td>
<td>
#Html.ListBox("lbSoundChipsetsSelected",(IEnumerable<SelectListItem>)ViewBag.SelectedSoundChipsets, new {#id="lbSoundChipsetsSelected", #style="min-width: 250px;"})
</td>
</tr>
</table>
</div>
<div class="form-group">
<label asp-for="MidiSynthChipsets" class="control-label"></label>
<table>
<tr>
<th>Available</th>
<th>↔</th>
<th>Selected</th>
</tr>
<tr>
<td>
#Html.ListBox("lbAllMidiChipsets",(IEnumerable<SelectListItem>)ViewBag.MidiSynthChipsets, new {#id="lbAllMidiChipsets", #style="min-width: 250px;"})
</td>
<td>
<input onclick="Javascript:SwitchListBoxItems('lbAllMidiChipsets', 'lbMidiChipsetsSelected');" type="button" value="→" /><br />
<input onclick="Javascript:SwitchListBoxItems('lbMidiChipsetsSelected', 'lbAllMidiChipsets');" type="button" value="←" />
</td>
<td>
#Html.ListBox("lbMidiChipsetsSelected",(IEnumerable<SelectListItem>)ViewBag.SelectedMidiSynthChipsets, new {#id="lbMidiChipsetsSelected", #style="min-width: 250px;"})
</td>
</tr>
</table>
And the JS code:
function SwitchListBoxItems(sourceListItem, targetListItem) {
var src = document.getElementById(sourceListItem);
var dest = document.getElementById(targetListItem);
if (dest != null && src != null) {
while (src.options.selectedIndex >= 0) {
var lstItemNew = new Option(); // Create a new instance of ListItem
lstItemNew.text = src.options[src.options.selectedIndex].text;
lstItemNew.value = src.options[src.options.selectedIndex].value;
dest.options[dest.length] = lstItemNew;
src.remove(src.options.selectedIndex);
}
}
So yeah, if someone can point me in the right direction to get the system to delete items.
Thanks in advance.

In a Razor View's #html.ActionLink, how do we pass the value of a dropdown list?

So what I am trying to do is that I have a database table of Roles, that I want to display in a dropdown list, and send the value to a different controller function. However, when I try to do this, I do not receive the value of the new Role that is selected from my dropdown list, but instead the value that was previously in my Model.
Here is my code for CSHTML:
#model IEnumerable<OnlineStoreData.Model.User>
<h4>List of Users: </h4>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.UserName)
</th>
<th></th>
</tr>
#foreach (var user in Model) {
if (user.Role.RoleName.TrimEnd(' ') == "User")
{
<tr>
<td>
#Html.DisplayFor(modelItem => user.UserName)
</td>
<td>
#Html.DropDownListFor(modelItem => user.Role.RoleName, new SelectList(ViewBag.RoleList)) //Here I need to select a new Role, for example "Admin"
#Html.ActionLink("Promote", "Promote", new { id = user.UserId, role = user.Role.RoleName }) |
#Html.ActionLink("Delete", "Delete", new { id = user.UserId })
</td>
</tr>
}
}
</table>
and this is the code for my Controller
public ActionResult ManageUsers()
{
ViewBag.RoleList = storeDBEntities.Roles.Select(role => role.RoleName).ToList();
return View(storeDBEntities.Users.ToList());
}
public ActionResult Promote(int id, string role)
{
//Here I should get the new role selected in the dropdown list, but I keep getting "User", which is the old role.
User toPromUser = storeDBEntities.Users.Find(id);
Role newRole = storeDBEntities.Roles.FirstOrDefault(r => r.RoleName == role);
if(toPromUser != null && newRole != null)
{
toPromUser.Role = newRole;
toPromUser.UserRole = newRole.RoleId;
storeDBEntities.SaveChanges();
}
return RedirectToAction("ManageUsers", "Users");
}
I am not sure how I should fix this to get the code to perform what is intended. Thank you.
The problem is, that you cannot dynamically append the selected value of your dropdown to your action link without JavaScript.
I think a more elegant approach would be to put your drop down and action buttons in a <form>. That way the method can also be a post, which is a bit more nice in a way, as get operations should not manipulate data.
<td>
<form method="post" action="#Url.Action("Promote", new { id = user.UserId })">
#Html.DropDownList("role", new SelectList(ViewBag.RoleList))
<button type="submit">Promote</button>
|
#Html.ActionLink("Delete", "Delete", new { id = user.UserId })
</form>
</td>
Note that the name of your drop down list should match the name of the role parameter of your controller.
When that works, you could then add the [HttpPost] attribute to your Promote action, to clarify that this method changes something.
For your delete action you could do something similar. Either make a second <form> with a different URL, or make it a submit button, too, in the same form, and give the buttons each a name and value.
The value of the button you clicked on will be sent to the server - note that I changed the form action URL:
<td>
<form method="post" action="#Url.Action("Update", new { id = user.UserId })">
#Html.DropDownList("role", new SelectList(ViewBag.RoleList))
<button type="submit" name="operation" value="promote">Promote</button>
|
<button type="submit" name="operation" value="delete">Delete</button>
</form>
</td>
And then decide what to do in the controller:
[HttpPost]
public ActionResult Update(int id, string operation, string role)
{
...
Lastly, you probably want a confirmation message on the delete action, which can be done so:
<button type="submit" name="operation" value="delete" onclick="return confirm('Do you really want to delete this user?');">Delete</button>

Model retains it's value after Submit to server

I'm facing this weird problem ,unable to make sense of it,I have a form which accepts Person Id and then reads the data from an API and fills the UI for person Edit purposes.
Here is the markup of this form,I'm guessing its has something to do with Model binding as I have two Form tag and both having the same Model Id.
#using (Html.BeginForm("UpdatePerson", "Person", FormMethod.Get))
{
<table>
<tr>
<td colspan="2">
<h3>Read Person for Edit</h3>
</td>
</tr>
<tr>
<td>
<label>#Html.LabelFor(m => m.Id)</label>
</td>
<td>
#Html.TextBoxFor(m => m.Id)
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" name="btnReadPerson" value="Read Person" />
</td>
</tr>
</table>
}
#using (Html.BeginForm("UpdatePerson", "Person", FormMethod.Post))
{
<table>
<tr>
<td>
<label>#Html.LabelFor(m => m.Id)</label>
</td>
<td>
#Html.TextBoxFor(m => m.Id, new { #readonly = "readonly" })
</td>
</tr>
<tr>
<td>
<label>#Html.LabelFor(m => m.Type)</label>
</td>
<td>
#Html.TextBoxFor(m => m.Type)
</td>
</tr>
I have stripped the view,I tried to kept it brief.
Below is the Action which handles the Get
[HttpGet]
[ActionName("UpdatePerson")]
public ActionResult UpdatePersonRead(PersonEditModel model)
{
if (model.Id.HasValue)
{
var apiClient = new ApiClient (ApiVersions.v1);
var segmentReplaceList = new Dictionary<string, string> { { "{id}", model.Id.Value.ToString() } };
bool ApiHitStatus = false;
var result = apiClient.MakeAPIRequest(out ApiHitStatus, ResourceUriKey.Person, segmentReplaceList, HttpVerbs.Get, string.Empty);
model = new PersonEditModel();
if (ApiHitStatus)
{
var personToBeUpdated = JsonConvert.DeserializeObject<RootChildPerson>(result);
if (personToBeUpdated != null)//Assigning json obj to model
{
model.NameFirst = personToBeUpdated.name_first;
model.NameLast = personToBeUpdated.name_last;
model.NameMiddle = personToBeUpdated.name_middle;
model.SocialSecurityNumber = personToBeUpdated.social_security_number;
model.SubType = PersonHelper.SubTypeValue(personToBeUpdated.sub_type);
model.Type = "person";
model.DateOfBirth = personToBeUpdated.date_of_birth;
model.Id = personToBeUpdated.id;
}
}
}
return View(model);
}
Now since the Person Id 4 does not corresponds to any person ,so I receive Null json object which upon conversion to C# class results in an empty (not null because it has every property set to null or empty) personToBeUpdated object which is then assigned to the model,I have checked model.Id becomes null in the Controller and even in the View ,but somehow it assigns input value that is 4 (it was null) to both Person Id textboxes.
Kindly let me know whats happening here.
Well as commented by #StephenMuecke ,So I cleared model before updating it.
model = new PersonEditModel();
ModelState.Clear();
Its also interesting to note that view takes data from ModelState instead of current specified model,
HtmlHelpers controls (like .TextBoxFor() etc.) don't bind to model values on Postback, but rather get their value directly out of the POST buffer from ModelState.
Taken from ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Passing the selected value of the radiobutton to action link

I am very new to MVC, I have to display the value coming from the database in a table format and show the radio buttons in front of each display so that user can select whatever option they want to choose, I need to post that option to the controller. Below is what I am doing.
#model IList<CertificateModel>
#{
ViewBag.Title = "RefreshCertificates";
}
<h2>
RefreshCertificates</h2>
#using (Html.BeginForm())
{
<table cellpadding="1" style="text-align: center; border: 5 px">
<tr>
<td>
</td>
<td>
Name
</td>
<td>
Issuer
</td>
</tr>
#for (var i = 0; i <= Model.Count - 1; i++) {
#Html.HiddenFor(x=>x[i].Subject)
<tr>
<td>
#Html.RadioButtonFor(x => x[i].Subject, true, new { #name = "optionsRadios", #id = "rbtrue" })
</td>
<td>
#Html.DisplayFor(x => x[i].Subject)
</td>
<td>
#Html.DisplayFor(x => x[i].Issuer)
</td>
</tr>
}
</table>
}
<table>
<tbody>
<tr>
<td>
#Html.ActionLink("STANDARD", "SelectCertOk", "LogIn", new { Type = "STANDARD", }, new { #class = "button" })
</td>
<td>
</td>
</tr>
</tbody>
</table>
Below is my model
public class CertificateModel
{
public string Subject
{
get { return cert.Subject; }
}
public string Issuer
{
get { return cert.Issuer; }
}
public bool validCert { get; set; }
}
My Controller that is putting the data on the screen code is below:
public ActionResult RefreshCertificates()
{
certificates = new List<CertificateModel>();
// some code here to fill up the list
return View(certificates );
}
The output that is displayed on the page is like this(RB is a radio button)
Subject Issuer
RB Coffee Test1
RB Tea Test2
From the current database only two are output on the screen. the user is only supposed to select only one of the radio button and then hit the actionLink button.
My problem is that right now both the buttons are selected, i want only one of the radio button to be selected and also, I also want the value of that radio button to be posted to the controller.
so for e.g if the user selects Coffee and Test1 radio button then I want to pass
#Html.ActionLink("STANDARD", "SelectCertOk", "LogIn", new { Type = "STANDARD", }, new { #class = "button" })
Type=STANDARD and SubjectIssue Coffee,Test1 to the controller. My controller signature is like this
public void SelectCertOk(string Type, string SubjectIssue)
{
}
any help will be greatly appreciated.
You are doing the right thing, but unfortunately we can't override the name attribute like the id.
Update your view to include multiple radiobuttons with same 'name'.
#Html.RadioButton('Subject', x[i].Subject)
STANDARD
<script>
function SelectCertOk(){
var subject = $("input:radio[name=Subject]")
$.post("#Url.Content("~/ControllerName/SelectCertOk")", { SubjectIssue : subject}, function (data) {}
});
}
</script>
But it won't solve your purpose.
So i would prefer to use javascript to check/uncheck radiobuttons.

Asp.MVC RenderAction for part of the page doesn't work as I expect

Ok I mess my page really bad and I need good help here.
I have a login page with Login and Register partial views.
RegisterView has it's own ViewModel class and it makes me problem.
I render Register partial view by using "RenderAction" helper.
Biggest problem is validation. When I don't enter anything in register fields and click submit register partial view is not updated inside it's parrent LoginView but return me only RegisterView. In other words my validation works but not where I want it to work.
LogOn.cshtml (this one contains register partial view)
<div id="regstrationForm">
#{Html.RenderAction("RegisterUser", "Account");}
</div>
Register (Partial View)
#using (#Ajax.BeginForm("RegisterUser", "Account", new AjaxOptions { UpdateTargetId = "reg" }))
{
<table id="reg">
...
<tr>
<td>
#Html.TextBoxFor(u => u.Username, new { #class = "registrationTextFields", placeholder = "Email" })
</td>
</tr>
<tr>
<td>
#Html.TextBoxFor(p => p.Password, new { #class = "registrationTextFields", placeholder = "Password" })
</td>
</tr>
<tr>
<td align="right">
<input class="signUpButton" type="submit" value="Sign up" />
</td>
</tr>
<tr>
<td>
#Html.ValidationMessage("myerrorsummary")
</td>
</tr>
</table>
This is HttpPost controller method when user click on sign up button.
Here I tried to return "PartialView()" and it turns my input fields to red css style but not display validation information bellow those fields.
[HttpPost]
public ActionResult RegisterUser(RegisterViewModel logOnViewModel)
{
if (ModelState.IsValid)
{
MembershipCreateStatus result;
try
{
...
}
else
{
ModelState.AddModelError("myerrorsummary", "The input is not valid");
ViewBag.CountryList = countryRepository.GetAllCountries();
ViewBag.CityList = cityRepository.GetAllCitiesByCountryId(1);
return View(logOnViewModel);
}
}
I suspect that you forgot to include the following script in your main view:
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
This is what makes Html helpers such as Ajax.BeginForm and Ajax.ActionLink to perform AJAX requests instead of normal.
You might also take a look at the following article which explains in more details about how those helpers work in ASP.NET MVC 3.

Resources