How to receive IEnumerable<int> from client? [duplicate] - javascript

This question already has answers here:
Pass an array of integers to ASP.NET Web API?
(18 answers)
Closed 3 years ago.
I have the following code. I'd like to do away with ContactIdsString, but I don't then know how to send the int[] in JavaScript to an IEnumerable in C#. Is there any way?
Html:
#model MyNamespace.Models.MassMailViewModel
#section scripts
{
#Scripts.Render("~/bundles/mass-mail")
<script>
var contactIdsName = '#nameof(MassMailViewModel.ContactIdsString)'
</script>
}
#using (Html.BeginForm(nameof(MassMailController.SendMail), "MassMail", FormMethod.Post, new { id = "massMailForm" }))
{
#(Html.Kendo().Button()
.Name("massMailButton")
.Content("Send")
.HtmlAttributes(new { type = "submit" })
)
#Html.HiddenFor(m => m.ContactIdsString)
...bunch of code for contact-mass-mail-grid...
}
JavaScript:
window.jQuery(function () {
window.jQuery("#massMailForm").submit(function () {
var ids = $('#contact-mass-mail-grid').data('kendoGrid').selectedKeyNames();
var idsJson = JSON.stringify(ids);
var hiddenField = $('#' + window.contactIdsName);
hiddenField.val(idsJson);
});
});
View Model:
public class MassMailViewModel
{
public string ContactIdsString { get; set; }//TODO I'd like to not have to do this.
public IEnumerable<int> ContactIds => JsonConvert.DeserializeObject<IEnumerable<int>>(ContactIdsString);
}
Controller:
public ActionResult SendMail(MassMailViewModel vm)
{
...
}

It looks like you are stopping the value from being posted back in your javascript here:
window.jQuery("#massMailForm").submit
Since you have the ContactIdsString data hidden within the form here:
#Html.HiddenFor(m => m.ContactIdsString)
Why not just let the form submit by removing the submit event handler?
If you are not wanting to do that you would have to submit the data via an ajax call.
Look here for more ajax info https://developer.mozilla.org/en-US/docs/Web/Guide/AJAX

I'm not actually sure you can accomplish what you want without changing your HTML, and I don't think you show enough of your HTML to know really what would need to be changed. The hidden field is just an input, so you really only have one value you can store in it. I'm not sure the razer engine allows you to go to/from an array in a single input.
But, what you could do low impact is create a new getter and leave the ContactIdsString.
public class MassMailViewModel
{
public string ContactIdsString { get; set; }//TODO I'd like to not have to do this.
public IEnumerable<int> ContactIds => this.ContactIdsString.Split(',').Select(n => int.Parse(n));
}
If you truly wanted to get rid of it entirely you'd have to follow the link for what #Kenneth K. suggests

I think you have a slight misunderstanding of the way that IEnumerable works. IEnumerable is for exposing an enumerator that will act on a set of materialized data. In this case, the data being sent to the server from the client is materialized, so there is no need to attempt to define it with an IEnumerable.
The model binder for ASP.NET MVC will attempt to initialize the values sent though, so just like IEnumerable<int> numbers = new int[]{1,2,3}; will work, so will accepting an array of integers into that IEnumerable.
All you need to do is follow the process of posting an array of integers to the server, which is why this question was closed as a duplicate of a question seeking that answer.

Related

C# Blazor Form: What would be the most elegant approach for autocompleting input?

In our previous ASP.NET MVC projects we used to rely on Ajax and jQuery to autocomplete values being inputted in to the form (such as getting a list of staff names to choose from whilst still typing) - pure JavaScript example of this.
I'm now creating a new project from scratch in Blazor Server model (.NET 6), and this last bit of work just doesn't seem to be achievable in an elegant way. Or should I say, it works well for a single field, but the trouble comes the approach should be used for multiple fields on the same page.
So the logic is following:
Make <InputText> control hidden and bound the value to the value in the EF data model.
Add <input> control with unique id and #ref tags and make it call C# autocomplete method
Once the input field is clicked, the C# autocomplete method then calls a JavaScript function using IJSObjectReference, that deals with the autocomplete logic (provides suggested values based on what's been typed in so far)
Once user chooses one of the suggested values (i.e. user name), JavaScript is then returning user ID via JSInvokable method in C#
The JSInvokable C# method then populates the relevant user ID value in the data model, and so this value is then going to be updated in both UI and database via EF.
The approach itself is the same as in the official Blazor documentation and it works, like I said.
However, my issue is making it work in some elegant way for multiple input fields on the same form. Is there even an elegant way to achieve this, if I just want to run a pure JavaScript and not jQuery or Ajax?
The biggest question is how do I make C# side of things to decide which input field relates to which field in the data model, whilst making JavaScript calls in between? I would like to avoid relying on hardcoded strings and naming conventions as well as (ideally) I would like to avoid creating multiple ElementReferences and JSInvokable methods essentially doing the same job.
Or perhaps my choice of pure JavaScript for this kind of task within Blazor project is essentially wrong and there are much better alternatives?
Here's some code excerpts (omitting non-essential parts), that hopefully will make it easier to understand.
HTML:
<div class="form-group">
<label>User</label>
<div class="autocomplete">
<InputText class="form-control" type="hidden" #bind-Value="data.UserId" />
<input id="#string.Format("{0}{1}", "userAutocomplete", autocompleteId)"
type="text"
name="#string.Format("{0}{1}", "userAutocomplete", autocompleteId)"
#onclick="AutocompleteUsers"
#ref="userAutocomplete" />
</div>
</div>
C#:
public Data? data { get; set; }
private ElementReference userAutocomplete;
private DotNetObjectReference<RazorPage>? dotNetHelper;
private IJSObjectReference? autocompletelJSObjectReference;
private string autocompleteId = $"_{Guid.NewGuid().ToString().Replace("-", "")}";
private async Task AutocompleteUsers()
{
try
{
dotNetHelper = DotNetObjectReference.Create(this);
autocompletelJSObjectReference = await JS.InvokeAsync<IJSObjectReference>("import", "./js/autocomplete.js");
await autocompletelJSObjectReference.InvokeVoidAsync("Autocomplete", dotNetHelper, userAutocomplete, userListJSON, autocompleteId);
}
}
[JSInvokable]
public async Task UpdateAutocomplete(string _userId, string _autocompleteId)
{
data.UserId = _userId;
}
JavaScript:
export function Autocomplete(dotNetHelper, inp, userList, autocompleteId)
{
inp.addEventListener("input", function (e)
{
var userListJSON = JSON.parse(userList);
for (i = 0; i < userList.length; i++)
{
var dict = userListJSON[i];
b.addEventListener("click", function (e)
{
inp.value = this.getElementsByTagName("input")[0].value;
dotNetHelper.invokeMethodAsync("UpdateAutocomplete", inp.value, autocompleteId);
});
}
});
}
With Blazor you need to rely less on JavaScript. Often functionality such as Autocomplete have already been implemented in Blazor free UI frameworks. I recommend you choose one as it will save you time and energy.
One of amazing free frameworks that I recommend is MudBlazor. Below is an example of how to implement Autocomplete with this framework such as the one shown in w3schools example you provided.
First you need to have a list of options, this can be a List or Enum
Enum for Countries:
You can get this list from https://gist.github.com/jplwood/4f77b55cfedf2820dce0dfcd3ee0c3ea and change attribute to Display(Name = "country").
public enum Countries
{
[Display(Name = "Afghanistan")] AF = 1,
[Display(Name = "Ă…land Islands")] AX = 2,
[Display(Name = "Albania")] AL = 3,
[Display(Name = "Algeria")] DZ = 4,
[Display(Name = "American Samoa")] AS = 5,
[Display(Name = "Andorra")] AD = 6,
// ...
}
Create Enum extension to get country names:
public static class EnumExtensions
{
public static string GetCountryName(this Countries country)
{
return (country == 0) ? String.Empty :
country.GetType()
.GetMember(country.ToString())
.First()
.GetCustomAttribute<DisplayAttribute>()
.GetName();
}
}
Page with Autocomplete Field:
#page "/"
#using MudBlazorTemplates2.Enums
#using Microsoft.AspNetCore.Components
#using MudBlazorTemplates2.Extensions
<PageTitle>Index</PageTitle>
<MudForm Model="#location">
<MudAutocomplete T="Countries"
Label="Country"
#bind-Value="location.Country"
For="#(() => location.Country)"
Variant="#Variant.Outlined"
SearchFunc="#Search"
ResetValueOnEmptyText="true"
CoerceText="true" CoerceValue="true"
AdornmentIcon="#Icons.Material.Filled.Search"
AdornmentColor="Color.Secondary"
ToStringFunc="#(c => c.GetCountryName())"/>
</MudForm>
<br/>
<p>You selected :#location.Country.GetCountryName()</p>
#code {
private Location location = new Location();
private async Task<IEnumerable<Countries>> Search(string value)
{
var countries = new List<Countries>();
foreach (Countries country in Enum.GetValues(typeof(Countries)))
countries.Add(country);
if (string.IsNullOrEmpty(value))
return null;
// customize if needed
return countries.Where(c => c.GetCountryName()
.StartsWith(value, StringComparison.InvariantCultureIgnoreCase));
}
public class Location
{
public Countries Country { get; set; }
}
}
Output:
You can find more simpler examples for Autocomplete at https://mudblazor.com/components/autocomplete#usage

Is there a way to pass an Arraylist from a Controller class to Javascript in a JSP page in a Spring Application? [duplicate]

This question already has an answer here:
Display data in jsp
(1 answer)
Closed 4 years ago.
I'm trying to pass an ArrayList of objects from a controller class to an array in Javascript when a particular #RequestMapping value in the mapping controller is called. Typically if I wanted to print the contents of the ArrayList from the java side I would use model.addAttribute("events", events); like below and have a c:foreach loop with c:out to print the values on the welcome page when the / or welcome mapping route is called. However, I want to store the objects in an array in javascript instead and I'm finding little information online about doing this within a Spring Framework.
I want to do this as the objects contain latitude and longitude values that I plan to use to print to print markers on a map. Any suggestions?
#RequestMapping(value = {"/", "/welcome"}, method = RequestMethod.GET)
public String welcome(Model model) {
ArrayList<Event> events = seeEvents.getAllEvents();
System.out.println(events);
model.addAttribute("events", events);
return "welcome";
}
Either you create an API that serve events in json and you use an "ajax call" in your application to get it
#RequestMapping(value = "/events", method = RequestMethod.GET)
public List<Event> events() {
return seeEvents.getAllEvents();
}
And on (modern) javascript:
fetch("/myContext/events").then(function(response) {
return response.json().then(function(json) {
// Do your thing...
});
another possibility is just to set in your JSP a code to create a static array:
var events=[<c:forEach ...>"<c:out ...>",</c:forEach>];

ASP.NET MVC 5 best practise form submit

Description:
I have a form for user-friendly input:
But i can't submit form in this way, coz my model for this form action looks like:
public string Title { get; set; } // First input
public string Description { get; set; } // Second input
public string SportsmanId { get; set; } // Not used
public List<WorkoutExerciseParam> WorkoutExerciseParams { get; set; } // Items, combined from list items (show on form screenshot)
public SelectList AvailableSportsmans { get; set; } // Dropdown list
So, if I can't submit, I wrote JS code to construct and post consistent model:
$(document)
.ready(function() {
$("#submit").click(function() {
var exerciseList = [];
/* Assemble exerciseList from OL and UL items */
var title = $("input#Title").val();
var description = $("input#Description").val();
var sportsmanId = $("select#SportsmanId").val();
$.post('#Url.Action("Create", "Workout")',
{
Title: title,
Description: description,
SportsmanId: sportsmanId,
WorkoutExerciseParams: exerciseList
});
});
});
This code works fine, but I can't redirect after the action is done (like when I just submit the form):
Then, I rewrite JS code so, that it constructs a new hidden form with hidden input and submit it. But I don't know how to create the list of inputs (List from first code sample).
Question:
What is the best practice to submit data to ASP.NET Controller's Action throw JS that I can use RedirectToAction() and View() methods?
Do I need construct form (how can I do a list of objects) or how handle RedirectToAction() and View() method in JS?
You should be making a normal submit rather that ajax if you want to redirect (or be able to return the view and display validation errors if ModelState is invalid. There is no point using ajax, since ajax calls do not redirect.
You have not shown how your dynamically generating the inputs associated with your WorkoutExerciseParam collection, but they just need to be named correctly with indexers so that they will be bound by the DefaultModelBinder. The format needs to be
<input name="WorkoutExerciseParams[0].SomeProperty" .... />
<input name="WorkoutExerciseParams[0].AnotherProperty" .... />
<input name="WorkoutExerciseParams[1].SomeProperty" .... />
<input name="WorkoutExerciseParams[1].AnotherProperty" .... />
....
Your can generate this using javascript, but a better solution which gives you string type binding, client side validation for the dynamically added items and the ability to also delete items is to use the BeginCollectionItem() method as discussed in the answers to
Submit same Partial View called multiple times data to
controller?
and
A Partial View passing a collection using the
Html.BeginCollectionItem
helper

Updating a Partial View in MVC 5

I am getting an error when trying to load a partial view that should display a list on the create view of the MVC app. The list is based on a value will come from a list of values drop control.
On create view there is no selection so the list is empty and will need to refreshed after the user selects a value while in the MVC create view.
I followed the accepted answer on this question and got errors:
Updating PartialView mvc 4
But I have some questions about what is being said.
Someone said: "There are some ways to do it. For example you may use jQuery:" and he shows the Java query.
But he also shows another method and says: "If you use logic in your action UpdatePoints() to update points"
[HttpPost]
public ActionResult UpdatePoints()
{
ViewBag.points = _Repository.Points;
return PartialView("UpdatePoints");
}
I get the following error
The parameters dictionary contains a null entry for parameter 'ID' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult UpdateList(Int32)' in 'System.Controllers.RController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter. Parameter name: parameters
I have no clue what this error means
So in create view:
<div class="col-sm-6">
<div class="form-horizontal" style="display:none" id="PVList">
#{ Html.RenderAction("UpdateList");}
</div>
</div>
In controller under the create action as its own function
[HttpGet]
public ActionResult UpdateList(int ID)
{
if (ID != 0)
{
ViewBag.List = Get_List(ID);
return PartialView("PV_List");
}
else
{
ViewBag.List = "";
return PartialView("");
}
}
And the function that makes the list for the view bag function:
private List<SQL_VIEW_LIST> Get_List(int ID)
{
return db.SQL_VIEW_LIST.Where(i => i.ID == ID).ToList();
}
The JavaScript for the for the list of values drop down list of values: That also controls turning on the visibility of the list when it has data:
//Fire The List to make visible after list values select
$(document).ready(function () {
$('#RES_VEH_ID').change(function ()
{
$("#PV_List").show(); // Shows Edit Message
$.post('#Url.Action("PostActionTo_Partial","Create")').always(function()
{ ('.target').load('/Create'); })
});
})
Also does anyone know what this string mean: ? "PostActionTo_Partial"
Also does anyone know what this means ViewBag.points = _Repository.Points; I get the view bag part but it's the _Repository.Points; part that I don't understand. Any one have any ideas of what is going on there?
I can't understand what do you try to do. But i'll try to answer.
I have no clue what this error means.
This error means that model binder can't find parameter "ID" for action method
public ActionResult UpdateList(int ID)
Because you don't send any parameter for this method:
You can try this:
#{ Html.RenderAction("UpdateList", new {ID="value"});}
Or you can set default value in your method:
public ActionResult UpdateList(int ID=value)
or make "ID" nullable:
public ActionResult UpdateList(int? ID)
Also does anyone know what this string mean: ? "PostActionTo_Partial"
this is "action name" in yor controller
Also does anyone know what this means ViewBag.points =
_Repository.Points;
it means assigning dynamic object "VivBag.points' data to transfer them into view
So with help from Matt Bodily You can Populate a Partial View in the create view triggered by a changed value in a drop down list using a view
bag and something called Ajax. Here is how I made my code work.
First the partial view code sample you need to check for null data
_WidgetListPartial
#if (#ViewBag.AList != null)
{
<table cellpadding="1" border="1">
<tr>
<th>
Widget Name
</th>
</tr>
#foreach (MvcProgramX.Models.LIST_FULL item in #ViewBag.AList)
{
<tr>
<td>
#item.WidgetName
</td>
</tr>
}
</table>
}
Populating your View Bag in your controller with a function
private List<DB_LIST_FULL> Get_List(int? VID)
{
return db.DB_LIST_FULL.Where(i => i.A_ID == VID).ToList();
}
In your Create controller add a structure like this using the [HttpGet] element
this will send you data and your partial view to the screen placeholder you have on your create screen The VID will be the ID from your Drop
down list this function also sends back the Partial View back to the create form screen
[HttpGet]
public ActionResult UpdatePartialViewList(int? VID)
{
ViewBag.AList = Get_List(VID);
return PartialView("_WidgetListPartial",ViewBag.AList);
}
I am not 100% if this is needed but I added to the the following to the ActionResult Create the form Id and the FormCollection so that I could
read the value from the drop down. Again the Ajax stuff may be taking care if it but just in case and the application seems to be working with
it.
This is in the [HttpPost]
public ActionResult Create(int RES_VID, FormCollection Collection, [Bind(Include = "... other form fields
This is in the [HttpGet] again this too may not be needed. This is reading a value from the form
UpdatePartialViewList(int.Parse(Collection["RES_VID"]));
On Your Create View Screen where you want your partial view to display
<div class="col-sm-6">
<div class="form-horizontal" style="display:none" id="PV_WidgetList">
#{ Html.RenderAction("UpdatePartialViewList");}
</div>
</div>
And finally the Ajax code behind that reads the click from the dropdown list. get the value of the selected item and passed the values back to
all of the controller code behind to build the list and send it to update the partial view and if there is data there it pass the partial view
with the update list to the create form.
$(document).ready(function () {
$('#RES_VID').change(function ()
{
debugger;
$.ajax(
{
url: '#Url.Action("UpdatePartialViewList")',
type: 'GET',
data: { VID: $('#RES_VID').val() },
success: function (partialView)
{
$('#PV_WidgetList').html(partialView);
$('#PV_WidgetList').show();
}
});
This many not be the best way to do it but this a a complete an tested answer as it work and it is every step of the process in hopes that no
one else has to go through the multi-day horror show I had to go through to get something that worked as initially based on the errors I thought
this could not be done in mvc and I would have to continue the app in webforms instead. Thanks again to everyone that helped me formulate this
solution!

Error using dynamic keyword in asp.net mvc 4

I am getting this long error when i accpet the parameter as dynamic on my server side action method in mvc 4.
{"Message":"An error has
occurred.","ExceptionMessage":"'Newtonsoft.Json.Linq.JObject' does not
contain a definition for
'TournamentId'","ExceptionType":"Microsoft.CSharp.RuntimeBinder.RuntimeBinderException","StackTrace":"
at CallSite.Target(Closure , CallSite , Object )\r\n at
System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite
site, T0 arg0)\r\n at
ManagerDeTorneos.Web.Controllers.TournamentDateController.Create(Object
data) in
F:\Prince\Projects\Juan\trunk\ManagerDeTorneos.Web\Controllers\TournamentDateController.cs:line
133\r\n at lambda_method(Closure , Object , Object[] )\r\n at
System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c_DisplayClass13.b_c(Object
instance, Object[] methodParameters)\r\n at
System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object
instance, Object[] arguments)\r\n at
System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1
func, CancellationToken cancellationToken)"}
[HttpPost]
public HttpResponseMessage AddMatch(dynamic data)
{
int tournamentDateId = (int)data.TournamentDateId.Value;
var tournamentDate = Catalog.TournamentDateRepository.GetById(tournamentDateId);
if (tournamentDate == null)
{
throw ExceptionHelper.NotFound("Fecha no encontrada!");
}
In The above method data Contains tournamentId as sent from ajax call as JSON.Stringify({'TournamentId':'5'}).
Can anybody tell me what is the cause of error. I even replaced the dll of Newtonsoft.Json as well
You are right dan but i fixed my issue by removing that dll from GAC. May be in GAC it was using old assembly
The error is caused by the fact that you typed your parameter as dynamic, which means that the model binder doesn't know what to make it. It's the same as if you were to declare it as an object. Since you are providing JSON, it serializes the object as a Json.Net JObject. Just because you define it as a dynamic doesn't mean that it's going to magically take whatever shape you need it to.
Change it to a concrete type - something that matches the structure of the provided JSON:
public class TournamentInfo
{
public int TournamentId { get; set; }
}
[HttpPost]
public HttpResponseMessage AddMatch(TournamentInfo data)
{
int tournamentDateId = data.TournamentId;
var tournamentDate = Catalog.TournamentDateRepository.GetById(tournamentDateId);
if (tournamentDate == null)
{
throw ExceptionHelper.NotFound("Fecha no encontrada!");
}
This way, the binder knows what it's supposed to turn the JSON into, and since TournamentInfo matches the structure of the JSON, it won't have any trouble serializing it.
Don't misuse dynamic. It was not introduced into C# so developers could stop defining classes.

Categories

Resources