Render component dynamically in Blazor using List<string> - frontend

I am trying to render component dynamically based on string List.
Let's List values which contains Component Name.
For example:
List<string> componentName = new List<string>() { "SurveyPrompt", "FetchData","Counter" };
Based on above list my component should render.
In Blazor, we have DynamicComponent in which it will generate Dynamic component based on component Type List. Below code is for reference.
Blazor page
<div class="container-fluid">
<div class="row" >
<div class="col-md-12">
<div class="d-flex flex-wrap" style="height: 200px;">
#foreach (var component in components)
{
<DynamicComponent Type=#component.type Parameters=#component.parameters />
}
</div>
</div>
</div>
</div>
Based on components list it going to render components
#code {
public List<(Type type, Dictionary<string, object> parameters)> components = new List<(Type type, Dictionary<string, object> parameters)>()
{ (typeof(SurveyPrompt), new Dictionary<string, object>() { }) };
}
Here, how can I pass component name as string and convert it into Blazor Component?

I have accomplish this by getting all assembly by default and check based on my string List data
public List<(Type type, Dictionary<string, object> parameters)> components = new List<(Type type, Dictionary<string, object> parameters)>();
Assembly assembly = Assembly.GetEntryAssembly();
Type[] assemblyType = assembly.GetTypes();
for (int i = 0; i < assignedFeatureList.Count; i++)
{
Type typeComponent = assemblyType.Where(x => x.Name.Contains(assignedFeatureList[i])).FirstOrDefault();
if (typeComponent != null)
{
(Type type, Dictionary<string, object> parameters) item;
item.type = typeComponent;
item.parameters = new Dictionary<string, object>();
components.Add(item);
}
}

You can wrap this functionality into a collection class with error checking:
public record ComponentData(Type ComponentType, Dictionary<string, object> Parameters) { }
public class ComponentList : IEnumerable<ComponentData>
{
private readonly List<ComponentData> _components = new List<ComponentData>();
private readonly Assembly _assembly;
public ComponentList(Assembly assembly)
=> _assembly = assembly;
public bool TryAdd(string componentName, Dictionary<string, object> parameters)
{
var comp = _assembly.GetTypes()
.SingleOrDefault(item => item.Name.Equals(componentName) && item.IsAssignableTo(typeof(Microsoft.AspNetCore.Components.IComponent)));
if (comp is not null)
_components.Add(new ComponentData(comp, parameters));
return comp is not null;
}
public IEnumerator<ComponentData> GetEnumerator()
=> _components.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
}

Related

Calling multiple Blazor components with two binding

I have a blazor component:
#inject IJSRuntime JSRuntime
#using bullDocDBAcess
#using bullDocDBAcess.Models
#inject ITagData _db
#if(tags == null){
<p><em>Loading...</em></p>
}else{
<input type="text" #bind=#tags data-role="tagsinput" />
<input type="hidden" #onchange="onChange" value=#tags />
}
#code{
private string tags = "";
private List<TagModel> tagsList;
private Task<IJSObjectReference> _module;
private Task<IJSObjectReference> Module => _module ??= JSRuntime.InvokeAsync<IJSObjectReference>("import", "./components/tagsinput/tags_inputs_imports.js").AsTask();
[Parameter]
public int organizationId { get; set; } = 0;
[Parameter]
public int documentId { get; set; } = 0;
[Parameter] public EventCallback<Notification> OnTagsChange { get; set; }
private void onChange(Microsoft.AspNetCore.Components.ChangeEventArgs args)
{
tags = (string) args.Value;
OnTagsChange.InvokeAsync(new Notification(tags, documentId.ToString()));
}
protected override async Task OnInitializedAsync()
{
if(organizationId == 0){ //inserção
tags = "";
}else{
tagsList = await _db.GetTagsFromDocument(organizationId, documentId);
foreach (TagModel t in tagsList){
if(String.IsNullOrEmpty(tags)){
tags += t.nome;
}else{
tags += "," + t.nome;
}
}
}
var module = await Module;
await module.InvokeVoidAsync("loadScripts");
}
public async ValueTask DisposeAsync()
{
if (_module != null)
{
var module = await _module;
await module.DisposeAsync();
}
}
}
Basically, the idea is to use a bootstrap input tag (called using jsinterop) and include it several times for each product in a table. When tags and updated in this table, the database should be updates with the tags for that specific values.
The parent component:
(...)
#for(int i=0;i<this.document.Count;i++)
{
DocumentModel p = this.document.ElementAt(i);
<tr>
<td><TagManagement #key=#p.id organizationId=#p.organizacao_id documentId=#p.id OnTagsChange="TagsHandler" /></td>
</tr>
}
(...)
void TagsHandler(Notification notification)
{
Console.WriteLine("Hello"); //only for testing purposes
}
A two-way binding is implemented between the parent and child. However, o have no idea how to handle multiple component invocation with callback function for each one. When a new tag is inserted, only works on the first element of the table and the event is fired two times (because in this test I only have two rows).
I tried to include the key attribute but didn't work. Can you please help me?
Best regards

RazorPages: Model does not get instantiated in Partial with Page Model

Im testing out RazorPages and .Net Core 2.1
I have just taken a new project template and have created a Partial.
These are relevant/added contents of the files.
My problem is
1) Immediate problem: In the partial: OnGetAsync (nor public void OnGet()) does not get called. and I get a NullReference-exceptiion in View on Model on line
#foreach (var item in Model.ImageBE) {
I have tried to cut out DB-call and excplicitly call OnGet from contructor but no difference.
2) I cant to find an example where the Page(index) has an instance of the Partials model (ImageGalleryModel below). but this is the only thing the compiler will accept. Am I doing this totally wrong?
Index.cshtml (the page)
...
[partial name="_ImageGallery" model="Model.ImageGallery" /]
...
Index.cshtml.cs
public class IndexModel : PageModel
{
ApplicationDbContext mContext;
public ImageGalleryModel ImageGallery;
public IndexModel(ApplicationDbContext context)
{
mContext = context;
ImageGallery = new ImageGalleryModel(mContext);
}
public void OnGet()
{
}
}
_ImageGallery.cshtml (the partial)
[table class="table"]
#foreach (var item in Model.ImageBE) {
...
_ImageGallery.cshtml.cs
public class ImageGalleryModel : PageModel
{
private readonly ApplicationDbContext _context;
public IList<ImageBE> ImageBE { get; set; }
public ImageGalleryModel(Photiqo.Data.ApplicationDbContext context)
{
_context = context;
}
public async Task OnGetAsync()
{
ImageBE = await _context.ImageBE.ToListAsync();
}
}
Partials should not have a PageModel file associated with them. If you have C# code that you want to execute, you should consider creating a ViewComponent.
Alternatively, you can move the public IList<ImageBE> ImageBE property to the IndexModel and instantiate it in the OnGetAsync method there. Then you can specify the model type on the partial and pass it to the partial using the tag helper as you currently re doing:
_ImageGallery.cshtml (the partial)
#model IList<ImageBE>
<table class="table">
#foreach (var item in Model) {
...

DropDownListFor in an MVC ASP application

I have a view model which you see below
public class AssetCategoryViewModel : IEnumerable<AssetCategory>
{
DataModelContext db = new DataModelContext();
public AssetCategory AssetCategory { get; set; }
public Guid Id { get; set; }
public IList<AssetCategory> AssetCategoryList { get; set; }
public IEnumerable<SelectListItem> AssetCategoryNames { get; set; }
public AssetCategoryViewModel()
{
AssetCategoryList = (from x in db.AssetCategory
select x).ToList();
AssetCategoryNames = (from x in db.AssetCategory
select new SelectListItem
{
Text = x.Name,
Value = x.Code
});
}
public IEnumerator<AssetCategory> GetEnumerator()
{
return AssetCategoryList.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
and I want to send SelectlistItem to to view for using dropdownlist.
action method :
public ActionResult AddNewCategory()
{
AssetCategoryViewModel acvm = new AssetCategoryViewModel();
return View(acvm);
}
and div class (included dropdownlistfor helper method)
<div class="form-group">
#Html.LabelFor(model => model.AssetCategory.MasterCategory, htmlAttributes: new { #class = "control-label" })
#Html.DropDownListFor(model => model.AssetCategoryNames, Model.AssetCategoryNames, "Seçim yapınız", htmlAttributes: new { #class = "form-control" })
</div>
and I got error
Server Error in '/' Application.
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
I am stuck on this.
I reccomend you to do this way to make dropdownlistfor
Instead of using view model you create a model class and add a function which retrieves the data from the database
your model class should be like,`
public list<selectListItem> function()
{
AssetCategoryList = (from x in db.AssetCategory
select x).ToList();
AssetCategoryNames = (from x in db.AssetCategory
select new SelectListItem
{
Text = x.Name,
Value = x.Code
});`
}
return list;
and it should return the value as list.
then the controller part should be
public ActionResult AddNewCategory()
{
AssetCategoryModel acm = new AssetCategoryModel();
viewmodel vm=new viewmodel();
vm.assetcatagorylist=acm.function();
return View("viewname",vm);
}
then you should make to render the data to the view by passing the viewmodel data to ur view,
your view be like
#using projectname.ViewModel
#model ViewModel
<html>
//your design part
#Html.DropDownListFor(m => m.assetcatagorylist, Model.assetcatagorylist, new { #id ="hi" })
</html>
IT WORKS PERFECTLY
CHEERS,
thank you very much.
I forgot to add new model to actionresult view.
I have changed the controller part as below and I worked
public ActionResult AddNewCategory()
{
AssetCategoryViewModel acvm = new AssetCategoryViewModel();
return View(acvm);
}
thanks.

HttpPostedFileBase Getting null when i use FileUpload control in MVC

This is my model class:
public class FileUploadModel
{
public HttpPostedFileBase File { get; set; }
}
This is My View:
#using VidesExample.Models
#model FileUploadModel
#{
ViewBag.Title = "FileUpload";
}
<h2>FileUpload</h2>
#using (Html.BeginForm("FileUpload","Sample"))
{
#Html.TextBoxFor(model=>model.File,new { type="file"})
<input type="submit" value="Upload" />
}
And this is my Controller:
public ActionResult FileUpload()
{
return View();
}
[HttpPost]
public ActionResult FileUpload(FileUploadModel obj)
{
var file=obj.File;
// if (video.File.ContentLength <= 0) return null;
// var fileName = Path.GetFileName(video.File.FileName);
// if (fileName == null) return null;
// var path = Path.Combine(Server.MapPath("~/Videos"), fileName);
// video.File.SaveAs(path);
//// return fileName;
return View();
}
When I try to getting the File it is displaying the null Value. and how to display and how to store the videos using Mvc.
Add below properties in form tag
method="post" enctype="multipart/form-data"
#using (Html.BeginForm("ActioName", "ControllerName", FormMethod.Post, new { enctype = "multipart/form-data" }))

Orchard Projection Page Default View

I am using Orchard 1.8 and have created a new content type (called PressRelease), query for the results and projection to view the query with a custom template (using URL Alternates in the format List-ProjectionPage-url-PressRelease.cshtml) and all of that is working fine.
The one part that has me stumped is, if I use The Theme Machine as my theme (untouched), this projection view will show up in an unordered list with the corresponding AutoRoute links to the individual ContentItem entities, their metadata and so on. I'm trying to figure out how I access things such as the AutoRoute URL for a specific item, the metadata (create/publish dates) and so on for use with things like a Facebook Share button. Essentially I'm trying to recreate that default view, albeit with customizations.
Here is the code for List-ProjectionPage-url-PressRelease.cshtml:
#using Orchard.Utility.Extensions;
#using System.Linq
#functions
{
public class PressRelease
{
public PressRelease()
{
this.Attachments = new List<Attachment>();
}
public string Title { get; set; }
public string Source { get; set; }
public DateTime PublishDate { get; set; }
public string Body { get; set; }
public List<Attachment> Attachments { get; set; }
}
public class Attachment
{
public string Filename { get; set; }
public string Path { get; set; }
}
}
#{
//add list of dynamic objects to strongly typed class
var releases = new List<PressRelease>();
foreach (var item in #Model.Items)
{
var release = new PressRelease
{
Title = item.ContentItem.TitlePart.Title,
Source = item.ContentItem.PressRelease.Source.Value,
PublishDate = item.ContentItem.PressRelease.Date.DateTime,
Body = item.ContentItem.BodyPart.Text
};
//load attachment(s) to class
var attachments = (Orchard.MediaLibrary.Fields.MediaLibraryPickerField)item.ContentItem.PressRelease.Attachment;
if (attachments.MediaParts.Count() > 0)
{
foreach (var part in attachments.MediaParts)
{
release.Attachments.Add(new Attachment { Filename = part.FileName, Path = part.MediaUrl });
}
}
releases.Add(release);
}
}
#{
foreach (var item in releases)
{
<div class="press-release">
<div class="press-release-title">#item.Title</div>
<div class="press-release-meta">
<span class="press-release-source">Source: #item.Source</span>
#if (item.PublishDate != DateTime.MinValue)
{
<span class="press-release-date">#item.PublishDate.ToShortDateString()</span>
}
</div>
#if (item.Attachments.Count() > 0)
{
<div class="press-release-attachments">
<span class="press-release-attachments-title">Attached: </span>
#foreach (var attachment in item.Attachments)
{
var linkText = attachment.Filename;
var url = attachment.Path;
#Html.Link(linkText, url);
if (attachment != item.Attachments.Last())
{
<span>, </span>
}
}
</div>
}
<div class="press-release-body">
<p>#Html.Raw(item.Body.Replace("\r\n", "<br />"))</p>
</div>
</div>
<div class="social">
<!-- ** This is where I need AutoRoute URL so I can do FB share link **-->
<div class="fb-share-button" data-href="" data-type="button_count"></div>
</div>
if (item != releases.Last())
{
<hr />
}
}
}
Thoughts?
Utilizing the Shape Tracer (in conjunction with #Bertrand's assistance in the comments above) helped me get to where I need. Here is the final layout code I went with (which has some super hacky stuff in it):
#using Orchard.Utility.Extensions;
#using System.Linq
#functions
{
public class PressRelease
{
public PressRelease()
{
this.Attachments = new List<Attachment>();
}
private string _NavigateUrl = string.Empty;
public string Title { get; set; }
public string Source { get; set; }
public DateTime PublishDate { get; set; }
public string Body { get; set; }
public List<Attachment> Attachments { get; set; }
public string NavigateUrl
{
get { return string.Format("{0}://{1}/{2}", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority, _NavigateUrl); }
set { this._NavigateUrl = value; }
}
}
public class Attachment
{
public string Filename { get; set; }
public string Path { get; set; }
}
}
#{
//add list of dynamic objects to strongly typed class
var releases = new List<PressRelease>();
foreach (var item in #Model.Items)
{
var release = new PressRelease
{
Title = item.ContentItem.TitlePart.Title,
Source = item.ContentItem.PressRelease.Source.Value,
PublishDate = item.ContentItem.PressRelease.Date.DateTime,
//this is super hacky to get a chopped version of the HTML submitted for a summary
Body = item.ContentItem.BodyPart.Text,
NavigateUrl = item.ContentItem.AutoroutePart.Path
};
//load attachment(s) to class
var attachments = (Orchard.MediaLibrary.Fields.MediaLibraryPickerField)item.ContentItem.PressRelease.Attachment;
if (attachments.MediaParts.Count() > 0)
{
foreach (var part in attachments.MediaParts)
{
release.Attachments.Add(new Attachment { Filename = part.FileName, Path = part.MediaUrl });
}
}
releases.Add(release);
}
}
#{
foreach (var item in releases)
{
<div class="press-release">
<div class="press-release-title">#item.Title</div>
<div class="press-release-meta">
<span class="press-release-source">Source: #item.Source</span>
#if (item.PublishDate != DateTime.MinValue)
{
<span class="press-release-date">#item.PublishDate.ToShortDateString()</span>
}
</div>
#if (item.Attachments.Count() > 0)
{
<div class="press-release-attachments">
<span class="press-release-attachments-title">Attached: </span>
#foreach (var attachment in item.Attachments)
{
#attachment.Filename
if (attachment != item.Attachments.Last())
{
<span>, </span>
}
}
</div>
}
<div class="press-release-body">
#{
var body = new HtmlString(Html.Excerpt(item.Body, 200).ToString().Replace(Environment.NewLine, "</p>" + Environment.NewLine + "<p>"));
<p>#body (read more)</p>
}
</div>
</div>
<div class="social">
<div class="fb-share-button" data-href="#item.NavigateUrl" data-type="button_count"></div>
</div>
if (item != releases.Last())
{
<hr />
}
}
}

Resources