I am very new to razor syntax & C# generally, and I am just working through a few blazor courses and got stuck getting my head around the use of a lambda in one of the examples (here for ref https://learn.microsoft.com/en-us/aspnet/core/tutorials/build-your-first-blazor-app?view=aspnetcore-3.1#build-a-todo-list
this bit:
<h3>Todo (#todos.Count(todo => !todo.IsDone))</h3>
The whole code is below... my problem is that I understand that it is evaluating whether members of the todos list are true/false, and then putting the count of these onto the page, and also that todo is a local variable within the Lambda (because if I change it to todoxxx => !todoxxx.IsDone it still works).
What I don't understand is how it is checking each entry in the todos list to evaluate it?
I apologise profusely in advance for what is probably a very simple question!
#page "/todo"
<h3>Todo (#todos.Count(todo => !todo.IsDone))</h3>
<ul>
#foreach (var todo in todos)
{
<li>
<input type="checkbox" #bind="todo.IsDone" />
<input #bind="todo.Title" />
</li>
}
</ul>
<input placeholder="Something todo" #bind="newTodo" />
<button #onclick="AddTodo">Add todo</button>
#code {
private IList<TodoItem> todos = new List<TodoItem>();
private string newTodo;
private void AddTodo()
{
if (!string.IsNullOrWhiteSpace(newTodo))
{
todos.Add(new TodoItem { Title = newTodo });
newTodo = string.Empty;
}
}
}
If you look at the source code for the IEnumerable.Count, you will see that, behind the scenes, it does exactly that:
Runs a foreach loop on the collection;
Tests your lambda code against every collection member;
If lambda results in true then the counter variable is incremented;
In the end, it returns the counter value.
In this particular context, your lambda is essentially treated like a function pointer, only this time with a strongly defined interface: it takes a single parameter of whatever type you have in your collection, and returns bool.
Regarding where the System.Linq reference comes from and how it is resolved without an explicit #using, it seems that Razor components are actually partial classes, with their other parts "hidden" (meaning autogenerated by the compiler). For your particular example, if you go to the \obj\Debug\netcoreapp3.1\Razor\Pages folder in your project, you will find there a file named "Todo.razor.g.cs", which contains the following:
namespace BlazorApp1.Pages
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
...
[Microsoft.AspNetCore.Components.RouteAttribute("/todo")]
public partial class Todo : Microsoft.AspNetCore.Components.ComponentBase
{
...
}
}
Which means that these 5 namespaces are always available in all components, regardless of the _Imports.razor contents. Not sure why these particular ones have been chosen, that's probably a question for Microsoft.
Related
<label for="client-list" class="label" style="display:inline">Client</label> <i class="fas fa-user-plus"></i> <br />
<input class="input field" value="#(GetClientName(#ClientID))" #oninput="#ClientSearchUpdated"/>
#if (AvailableClients != null)
{
<ul>
#foreach (var client in AvailableClients)
{
<li id="#client.Id" #onclick="#(e => SetClientID(e, client.Id))">#client.FullName</li>
}
</ul>
}
I want to be able to take the above markup/code and turn it into a reusable component that will create an input field that when typed in, will display a list of results based on what is typed in. These results will be of the type of List that is passed in. eg: if a List<Person> is passed in, it would search the DB for people matching the search terms that a user types in. In the generic version, the List holding the objects to be searched wouldn't be AvailableClients, nor the functions getting / updating information specific to clients, of course.
At the end of this, my goal is to be able to replace the above code fragment with:
<SearchableDropdown DropdownItems="AvailableClients"></SearchableDropdown>
(The fields that are searched are currently determined by the sproc used in each of the DataAccessObjects at the moment)
The problem I've come across trying to develop such a generic component is that I'm not super familiar with generics to begin with (I understand the base concepts, what the syntax for using generics is, etc, but I haven't created a lot of generic code), and especially not when it comes to integrating that concept with Blazor.
What I've tried so far is:
Using Inheritance to accept a generic List<ISearchableDropdownItem>, and the objects in my system would implement this Interface, which would have two members: ID and DropdownDisplayInfo which would allow for the dropdown to send information about which item is clicked, as well as give each item something to display for each item in the Search Results.
The problem with this approach is that I then have to create Interfaces for the DataAccess layer & the Services which I've created in my Blazor application as well. This is a long, cascading problem that will lead me down an interface-creation rabbit hole. I'm not even 100% sure if this solution will work in the end.
Use an #typeparam TDropdownItem directive to allow the type used throughout the component to be of that which is passed in.
The obvious problem with this is that it will still put a lot of responsibility on the person utilizing the SearchableDropdown component to give the appropriate markdown for the RenderFragment, plus that still leaves the problem of having generic, "GetObjectName(ObjectID)" and "ObjectSearchUpdated" functions for the component.
Is there a decently straightforward way of implementing this that I'm just completely missing? Am I even remotely on the right track, and it's just going to take a bunch of refactoring of existing code to make things work?
If it is not possible to implement interface approach, maybe you can try create a common proxy object to pass arguments.
For example, you may try to pass Id, Name and Delegate as a proxy list with LINQ.
Prepare your arguments within OnInitialized or wherever you initiate it.
Here is a simple concept which shows how you can use delegation.
Delegation, data and proxy class initialization:
public delegate void OnClick(UIEventArgs eventArgs, int id, string name);
private class ExampleData
{
// let's assume that it has different naming convention for your case
public int IdValue {get;set;}
public string Fullname {get;set;}
}
private class SearchableDropdownItem
{
public int Id {get;set;}
public string Name {get;set;}
public OnClick OnClicked {get;set;}
}
Example proxy creation:
public void Prepare()
{
var dataList = new List<ExampleData>();
for(int i=0; i<3; i++)
{
dataList.Add(new ExampleData(){
IdValue = i+1,
Fullname = "test" + (i + 1)
});
}
var searchableItemList = dataList.Select(x => new SearchableDropdownItem(){
// here is how you can set callback and fields
Id = x.IdValue,
Name = x.Fullname,
OnClicked = new OnClick(SetClientID)
}).ToList();
}
public void SetClientID(UIEventArgs eventArgs, int id, string name)
{
// implement your code
}
And pass searchableItemList to component and use it:
<SearchableDropdown ItemList="searchableItemList"></SearchableDropdown>
...
#foreach (var item in searchableItemList)
{
<li id="#item.Id" #onclick="#(e => item.OnClicked(e,client.Id,client.Name))">#item.Name</li>
}
I am a Java/Spring dev new to C#/Entity Framework and was wondering if there was a C# equivalent to Springs #ModelAttribute Annotation
ex:
In a java controller/servlet I can do
#ModelAttribute("form")
public IContactForm getContactForm() {
return new ContactForm();
}
or something like
#ModelAttribute("list")
public List getItems() {
return new ArrayList( ... );
}
and in the view/markup - reference said attribute by
<form:form path="myField">
or ( pseduo )
<select>
for (String s : list) {
<option value="${s}" />
}
</select>
Then, if i were to post said form I could create the ContactForm object through the use of #ModelAttribute() IContactForm form
ex:
public void handleJsonPost( #Valid #ModelAttribute("form") IContactForm form) {
String x = form.getAField();
}
So, to reiterate the quesiton, does .NET/Entity Framework have a built in functionality like Springs #ModelAttribute or is there a Nuget package I can download? ...or anything at all?
This is done using the Razor view engine. I suggest you reference this link to get started and scroll down to "Task 2 - Creating the Edit View" if you want to dive straight into forms.
I am trying to call IEnumerable method in my _Layout.cshtml file. At the final I was adviced to "use html.action - to call server method that populates collection and returns partial view".
Currently I have created partial file _Dodatki.cshtml, that contains call of IEnumerable method (Aktualnosci.cs is model file):
#model IEnumerable<DluzynaSzkola.Models.Aktualnosci>
In my _Layout.cshtml I called method from my constructor with:
#Html.Action("_Dodatki", "AktualnosciController ", new {area="" })
And at the final I want to create method in my AktualnosciConstructor.cs file. Currenly I have method:
[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
IList<Aktualnosci> lista = new IList<Aktualnosci>();
return PartialView("_Dodatki", lista);
}
Unfortunately, when using syntax as above, it gives me message in compiler:
"cannot create an instance of the abstract class or interface
'IList'".
When replacing 'IList' with 'List', it gives me exception:
"System.Web.HttpException: The controller for path '/' was not found
or does not implement IController."
I have no idea how in other way I can populate collection in the method.
edit: As per request, below AktualnosciController.cs definition, with no other methods:
namespace DluzynaSzkola.Controllers
{
public class AktualnosciController : Controller
{
//here are other methods
[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
IList<Aktualnosci> lista = new IList<Aktualnosci>();
return PartialView("_Dodatki", lista);
}
}
}
as noticed by GTown-Coder your controller name seems wrong. Updated my answer accordingly.
I think that your problem might be the same as answered by this SO post.
try specifying the Area name and, if this controller is not in an area simply add an empty area name.
#Html.Action("_Dodatki", "AktualnosciController ", new {area="" })
Even if this does not solve your problem it is good practice because if this view is latter used within an area it will try to find the controller in the area and not in the root space.
Allright, I have implemented changes to my project, that works fine.
My in _Layout.cshtml call is changed a bit. AktualnosciController supposed to be called just Aktualnosci !!!
<div class="kontenerDodatkiLayout hidden-xs hidden-sm">
<div class="archiwum">Archiwum</div>
#Html.Action("_Dodatki", "Aktualnosci", new { area = "" })
</div>
My partial view _Dodatki.cshtml model call is changed a bit:
#model IEnumerable<DateTime>
<div class="wpisDodatki">
#foreach (var item in Model)
{
<div> #Html.DisplayFor(modelItem => item)</div>
}
<p>test<br />test<br />test</p>
</div>
And method in my controller AktualnosciController.cs looks like that:
//[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
using (var context = new DluzynaContext())
{
var lista = context.Indeks.Select(it => it.Dzien).ToList();
return PartialView("_Dodatki", lista);
}
}
in here lista is passed to my partial view _Dodatki, and it is populated with context property Indeks and model property Dzien.
Thanks guys for your help #Wndrr , #GTown-Coder.
How can I set TextBox value in MVC5 from ViewBag which contains a list? As you can see my list is in Viewbag.photos and I want to have each value of photo.id in my TextBox and then pass it to controller
#foreach (var photo in ViewBag.photos)
{
#if (#photo.comment != null)
{
<h6>#photo.comment</h6>
}
else
{
<h6> - </h6>
}
#Html.TextBox("photoID", #photo.id)
}
Trying to do that I get an error:
Error CS1973 'HtmlHelper>' has no applicable method
named 'TextBox' but appears to have an extension method by that name.
Extension methods cannot be dinamically dispached.
Maybe there's another workaround?
This is happening because ViewBag.photos is a dynamic object. The compiler cannot know its type, so you have to manually cast it to its original type.
For example:
#Html.TextBox("photoID", (int)photo.id)
As a side note (I'm not sure whether this will prevent your code from working, but it's good practice anyway), you also have bit too many #s: to cite Visual Studio, once inside code, you do not need to prefix constructs like "if" with "#". So your final code will look like:
#foreach (var photo in ViewBag.photos)
{
if (photo.comment != null)
{
<h6>#photo.comment</h6>
}
else
{
<h6> - </h6>
}
#Html.TextBox("photoID", (int)photo.id)
}
You should also consider using ViewModels instead of ViewBag to pass data between your controllers and your views.
I am struggling a bit with passing list of object to C# code from view. If I pass a simple string it works fine. but its not working for List. So I am sure I am missing something here.
View
<div class="row control-actions">
#{
List<MyObject> test = ViewBag.ObjectsList;
<button type="button" class="btn btn-primary btn-wide" onclick="addAllObjectsToExp('#test')">Add All</button>
}
</div>
<script type="text/javascript">
function addAllObjectsToExp(objList) {
$.post('#Url.Action("AddAllObjectsToExp", "ExportObjects")', { objectsList: objList},
function (result) {
$('#numbers').html(result);
});
}
</script>
Code
[HttpPost]
[OutputCache(Location = System.Web.UI.OutputCacheLocation.None, NoStore = false, Duration = 0)]
public int AddAllObjectsToExp(List<MyObject> objectsList)
{
foreach(MyObject obj in objectList)
{
//Do something here
}
//and return an integer
}
While debugging I can see that the #test variable is getting populated with the list of MyObject. But when I reach the code side its always null.
Please let me know if i am missing something here. Also tell me if more information is needed.
You're passing a C# object into a Javascript function. It doesn't know how to read that. You should serialize it into JSON before passing it in.
If you use Json.NET you can do it by
ViewBag.ObjectsList = JsonConvert.SerializeObject(yourlist);
Then you can continue as you were.
Some notes:
You should try to start using ViewModels instead of putting things in the ViewBag. On the Javascript side you should bind event handlers for things like clicking instead of using onclick as it would make your code much more manageable and reusable.