Hi!
I have this scenario where I want to forward the current model's sub-object as a model to a DisplayTemplate within the Grid's ClientDetailTemplate: Checkout the first Tab of the TabStrip in the ClientDetailTemplate
Main View:
@(Html.Kendo().Grid(Model)
.Name("gridLetters")
.Columns(columns =>
{
columns.Bound(p => p.Id).ClientTemplate("<a href='" + Url.Action("View", "Letter", new { letterId = "#:Id#" }) + "'>#=Id#</i></a>");
columns.Bound(p => p.Subject);
columns.Bound(p => p.CompanyName).Title("Company");
columns.Bound(p => p.BrandName).Title("Brand");
columns.Bound(p => p.Location.Name).Title("Location");
columns.Bound(p => p.LetterType.Name).Title("RL Type");
})
.ToolBar(toolbar =>
{
toolbar.Search();
})
.Sortable()
.Navigatable()
.Resizable(r => r.Columns(true))
.Pageable(pageable => pageable
.Refresh(true)
.PageSizes(true)
.ButtonCount(5))
.Filterable()
.Scrollable()
.ClientDetailTemplateId("detailTemplate")
.HtmlAttributes(new { style = "height:430px;" })
.DataSource(dataSource => dataSource
.Ajax()
.Sort(x => x.Add("Id").Descending())
.PageSize(20)
.ServerOperation(false)
))
@section Scripts {
<script id="detailTemplate" type="text/kendo-tmpl">
@(Html.Kendo().TabStrip()
.Name("tabStrip_#=Id#")
.SelectedIndex(0)
.Animation(animation => animation.Open(open => open.Fade(FadeDirection.In)))
.Items(items =>
{
items.Add().Text("History").Content(@<text>@Html.DisplayFor(m => m.History, "LetterHistory")</text>);
items.Add().Text("Details").Content("");
}).ToClientTemplate())
</script>
}
Above, the model for the main grid is an IList of my objects. Each object has a History property of type
BusApp.Domain.Models.BusinessObjects.LetterActionHistoryFlatModel
I need to get that property passed on as a model for the DisplayFor
LetterHistory.cshtml
@model IList<BusApp.Domain.Models.BusinessObjects.LetterActionHistoryFlatModel>
@(Html.Kendo().Grid(Model)
.Name("gridLetterHistory")
.Columns(columns =>
{
columns.Bound(p => p.ActionStamp).Title("Stamp").Format("{0:dd-MMM-yyyy}");
columns.Bound(p => p.LetterActionName).Title("Action");
columns.Bound(p => p.ActionTakenByName).Title("Taken By");
})
.Navigatable()
.Resizable(r=>r.Columns(true))
.Pageable(pageable => pageable
.Refresh(true)
.PageSizes(true)
.ButtonCount(10))
.Filterable()
.Scrollable()
.DataSource(dataSource => dataSource
.Ajax()
.Sort(x => x.Add("ActionStamp").Descending())
.PageSize(20)
.ServerOperation(false)
)
)
Regrds.
The aspnet core binaries looked like this with the previous release. See below.
Now, it appears the net6.0 is gone.Why go backwards to netstandard2.0? Where was this explained? I feel like upgrading is a huge downgrade so I should NOT do it.
We were told this 2 years ago - https://visualstudiomagazine.com/articles/2020/09/16/net-standard-future.aspx .
hello,
what is the general pattern to include a grid inside another Grid's DetailTemplate? I have a heriarchical collections I need to populate. I don't see other way than declaring a script inside the template's script tag, but that is invalid html.
I have defined in my cshtml the charts with this
.DataSource(ds =>
{
ds.Read(read => read.Action(Model.ActionMethod, Model.ControllerActionMethod).Type(HttpVerbs.Post));
if (Model.SeriesGrouped)
{
//to create data groups
ds.Group(group => group.Add(model => model.Group)).Sort(s => s.Add(m => m.Order));
}
})
The class MyModelClass is like this
public class MyModelClass
{
public string Group { get; set; }
public string Category { get; set; }
public DateTime Order { get; set; }
}
When all groups of my datasource has values for all axies thats work fine, by example if my datasource result is like this:
{Group:"A", Order:"01/2021", value="1000"} {Group:"A", Order:"02/2021", value="1200"} {Group:"A", Order:"03/2021", value="1100"} {Group:"B", Order:"01/2021", value="2000"} {Group:"B", Order:"02/2021", value="2200"} {Group:"B", Order:"03/2021", value="2100"}
The chart displayed the axies in a good way, I mean the period is ordered by date (first jan, then feb and las march) " 01/2021 02/2021 03/2021"
But when one group of the datasource result doesn't have values in a period the beahavior is diferent, the order of axies will be created depending of order of groupin that telerik chart create. I will explain:
My datasource result is (i remove the first value to period 01/2021):
{Group:"A", Order:"02/2021", value="1200"} {Group:"A", Order:"03/2021", value="1100"} {Group:"B", Order:"01/2021", value="2000"} {Group:"B", Order:"02/2021", value="2200"} {Group:"B", Order:"03/2021", value="2100"}
then the axies on chart is displayed like this (firs feb, then march, then jan) " 02/2021 03/2021 01/2021 "
so the result final in my implementation is like this
this problem doesn't happen when all groups has data for all axies, the field used for create the axies is a datetime.
Hope I'm asking this question in the right place.
I have this working:
@(Html.Kendo().Menu()
.Name("QmsMenu")
.DataTextField("Title")
.DataSource(ds => ds.Read("Menu_Read", "Menu")
.Model(model => model.Children("MenuItems")))
)
Reference: https://demos.telerik.com/aspnet-core/menu/remote-data-binding
However, I can't figure out how to dynamically add links to each of the items in a Menu binding to remote data.
The payload returned from the server is providing the controller name and controller action method name for each of the menu items. I would like to add these values to the code above enabling a user to click on a menu link and be taken to a different page in the web application.
Would appreciate some help regarding this.
Hi I have a requirement to show a popup when a card is moved or dropped to another column say working to done. in the popup if i say no then the card has to be moved back to where it was before. Basically can i undo the movement after validation or can i move the object without manual intervention. Does there are methods to move the card from one column to another thru Javascript?
Thanks!
Venu
Using a controller based off of the sample code and the chunked upload configuration, I find that in local testing I eventually end up with an IO exception claiming another process is trying to access the file when I'm uploading larger files.
I suspect I am somehow having the AppendToFile() call run into the previous AppendToFile() call before it is done syncing to disk or something, but have no proof.
In addition, when the fallback Save() call is used (if I forget to set a chunk size) the files turn out corrupt, being only partially uploaded.
AV is disabled for the relevant directories and my Git services are not running, and so I'm not sure what else could be accessing the file. Is there a way to solve this issue?
I can go and try using the vanilla version of the sample code if I must but feel that there may be something else going on here regardless.
Thank you!
My Controller:
using Csla;
using Finbuckle.MultiTenant;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Newtonsoft.Json;
using Portal.Library;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
namespace WebUI.Controllers
{
public class FileStore : Csla.Web.Mvc.Controller
{
private string UploadBasePath = "wwwroot\\uploads";
private string UploadStoragePath = "wwwroot\\storage";
public WebUI.TenantInfo? TenantInfo { get; private set; }
public FileStore(IMultiTenantContextAccessor<WebUI.TenantInfo> mtaccessor)
{
TenantInfo = mtaccessor.MultiTenantContext.TenantInfo;
}
public class ChunkMetaData
{
public string UploadUid { get; set; }
public string FileName { get; set; }
public string RelativePath { get; set; }
public string ContentType { get; set; }
public long ChunkIndex { get; set; }
public long TotalChunks { get; set; }
public long TotalFileSize { get; set; }
}
public class FileResult
{
// Because the chunks are sent in a specific order,
// the server is expected to send back a response
// with the meta data of the chunk that is uploaded.
public bool uploaded { get; set; }
public string fileUid { get; set; }
public string fileStorageGuid { get; set; } // internal GUID in DB per file not per session
public string selector { get; set; } // because the UID isn't used or sent for the fallback Save()
}
public void AppendToFile(string fullPath, IFormFile content)
{
try
{
using (FileStream stream = new(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
{
content.CopyTo(stream);
}
}
catch (IOException ex)
{
throw ex;
}
}
public ActionResult ChunkSave(IEnumerable<IFormFile> files, string metaData)
{
if (metaData == null)
{
return Save(files);
}
MemoryStream ms = new(Encoding.UTF8.GetBytes(metaData));
JsonSerializer serializer = new();
ChunkMetaData chunkData;
using (StreamReader streamReader = new(ms))
{
chunkData = (ChunkMetaData)serializer.Deserialize(streamReader, typeof(ChunkMetaData));
}
string path = Path.Combine(UploadBasePath, chunkData.FileName);
// The Name of the Upload component is "files".
if (files != null)
{
foreach (var file in files)
{
AppendToFile(path, file);
}
}
var isUploaded = chunkData.TotalChunks - 1 <= chunkData.ChunkIndex;
FileResult fileBlob = new()
{
uploaded = isUploaded,
fileUid = chunkData.UploadUid
};
if (isUploaded)
{
var bytes = Encoding.UTF8.GetBytes(chunkData.FileName);
fileBlob.fileStorageGuid = CatalogAndMoveFileAsync(chunkData.FileName, path).ToString();
fileBlob.selector = Convert.ToBase64String(bytes);
}
return Json(fileBlob);
}
private Guid CatalogAndMoveFileAsync(string FileName, string FilePath)
{
//sanity
if (System.IO.File.Exists(FilePath))
{
// step 1 - generate guid in files table by inserting the requisite information
var _user = Csla.ApplicationContext.User as ClaimsPrincipal;
int userID = int.Parse(_user.Claims.FirstOrDefault(S => S.Type == "UserID").Value.ToString());
var finfo = new FileInfo(FilePath);
var obj = DataPortal.Create<FileStorage>();
obj.UserID = userID;
obj.Extension = Path.GetExtension(FileName);
obj.OrigName = FileName;
obj.KBSize = finfo.Length / 1024;
obj.SiteID = TenantInfo.SiteID;
// TODO: revisit and see if SaveObjectAsync updates our object v6
// use the object's own save function to update the object
obj = obj.Save();
// do we have to re-fetch or is the object guid updated automatically?
if (obj.ID != Guid.Empty)
{
// step 2 - determine new name, location, move to it
string StorageSubfolders = Path.Combine(UploadStoragePath, obj.RelPath);
try
{
System.IO.Directory.CreateDirectory(StorageSubfolders); // so glad this func does it in one go
var newFilePath = Path.Combine(StorageSubfolders, obj.InternalName);
System.IO.File.Move(FilePath, newFilePath);
if (System.IO.File.Exists(newFilePath))
{
// step 3 - return the guid
return obj.ID;
}
}
catch (Exception ex)
{
if (obj.ID != Guid.Empty)
{
DataPortal.Delete<FileStorage>(obj.ID, userID);
}
throw new FileNotFoundException("FileStorage moving file", ex);
}
}
}
return Guid.Empty;
}
public ActionResult RemoveAsync(string[] fileNames, string[] FileStorageUIDs)
{
// we don't actually care about fileNames, telerik always sends *something* related to that though
// we rely on our file UID(s) that were sent back and hopefully captured in the onSuccess event
// The parameter of the Remove action must be called "fileNames"
if (FileStorageUIDs.Length > 0)
{
foreach (var fUID in FileStorageUIDs)
{
// get the current UID to be 100% sure we're deleting only our own file
var _user = Csla.ApplicationContext.User as ClaimsPrincipal;
int userID = int.Parse(_user.Claims.FirstOrDefault(S => S.Type == "UserID").Value.ToString());
var obj = DataPortal.Fetch<FileStorage>(new Guid(fUID), userID);
if (obj.ID != Guid.Empty)
{
var basePath = Path.Combine(UploadStoragePath, obj.RelPath);
var physicalPath = Path.Combine(basePath, obj.InternalName);
if (System.IO.File.Exists(physicalPath))
{
System.IO.File.Delete(physicalPath);
//TODO: maybe try/catch?
if (Directory.EnumerateFiles(basePath).Any() == false)
{
Directory.Delete(basePath, true);
}
// we already fetched with UID
DataPortal.Delete<FileStorage>(obj.ID, userID);
}
}
}
}
// Return an empty string to signify success
return Content("");
}
// TODO: while this method fully works, for whatever reason it corrupts files when we fall back to it
public ActionResult Save(IEnumerable<IFormFile> files)
{
List<string> fileNames = new();
List<FileResult> results = new();
// The Name of the Upload component is "files".
if (files != null)
{
foreach (var file in files)
{
var fileContent = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
// Some browsers send file names with full path.
// The demo is interested only in the file name.
var fileName = Path.GetFileName(fileContent.FileName.ToString().Trim('"'));
var physicalPath = Path.Combine(UploadBasePath, fileName);
using var fileStream = new FileStream(physicalPath, FileMode.Create);
file.CopyToAsync(fileStream);
fileNames.Add(fileName);
}
// since we can't delete while in the scope of the loop there, we have to do a second one
foreach (var fileName in fileNames)
{
var physicalPath = Path.Combine(UploadBasePath, fileName);
var fileStorageGuid = CatalogAndMoveFileAsync(fileName, physicalPath);
string guidString = fileStorageGuid.ToString();
var bytes = Encoding.UTF8.GetBytes(fileName);
results.Add(new FileResult()
{
uploaded = true,
fileStorageGuid = guidString,
selector = Convert.ToBase64String(bytes)
});
}
return Json(results);
}
return Content("");
}
public async Task GetFile(string FileStorageUID)
{
var _user = Csla.ApplicationContext.User as ClaimsPrincipal;
int userID = int.Parse(_user.Claims.FirstOrDefault(S => S.Type == "UserID").Value.ToString());
// TODO: maybe add a func to the user dal to fetch hierarchy?
// TODO: check role here, if UID doesn't match, don't pass userID
var obj = await DataPortal.FetchAsync<FileStorage>(new Guid(FileStorageUID), 0); // uid is second param if needed
if (obj.ID != Guid.Empty)
{
var basePath = Path.Combine(UploadStoragePath, obj.RelPath);
var physicalPath = Path.Combine(basePath, obj.InternalName);
if (System.IO.File.Exists(physicalPath))
{
var provider = new FileExtensionContentTypeProvider();
if (!provider.TryGetContentType(physicalPath, out string contentType))
{
contentType = "application/octet-stream";
}
Response.Clear();
Response.Headers.Add("Content-Disposition", "inline;filename=" + obj.OrigName);
Response.ContentType = contentType;
await Response.SendFileAsync(physicalPath);
}
}
}
}
}
My View:
@(Html.Kendo().Upload()
.Name("files")
.Async(a => a
.Save("ChunkSave", "FileStore")
.Remove("Remove", "FileStore")
.AutoUpload(true)
.ChunkSize(10240)
)
.Validation(validation => validation
//.AllowedExtensions(new string[] { ".gif", ".jpg", ".png" })
.MaxFileSize(200000000) // 200 megs
//.MinFileSize(512)
)
.Events(events => events
.Success("onSuccess")
.Remove("onRemove")
.Upload("onUpload")
).Multiple(false) // one file and one file only
)
Hi!
I have the following setup:
CsHtml:
<div class="row mt-3">
<div class="col-lg-4">
@(Html.Kendo().DropDownListFor(m => m.CategoryHeadId)
.Size(ComponentSize.Medium)
.Rounded(Rounded.Medium)
.FillMode(FillMode.Solid)
.OptionLabel("Select head category...")
.HtmlAttributes(new { style = "width: 100%" })
.DataTextField("Name")
.DataValueField("Id")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetLookupCategoriesHead", "Api");
});
})
)
</div>
<div class="col-lg-4">
@(Html.Kendo().DropDownListFor(m => m.CategoryMainId)
.Size(ComponentSize.Medium)
.Rounded(Rounded.Medium)
.FillMode(FillMode.Solid)
.OptionLabel("Select main category...")
.HtmlAttributes(new { style = "width: 100%" })
.DataTextField("Name")
.DataValueField("Id")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetLookupCategoriesMain", "Api")
.Data("filterMainCategories");
})
.ServerFiltering(true);
})
.Enable(false)
.AutoBind(false)
.CascadeFrom("CategoryHeadId")
)
</div>
<div class="col-lg-4">
@(Html.Kendo().DropDownListFor(m => m.CategorySubId)
.Size(ComponentSize.Medium)
.Rounded(Rounded.Medium)
.FillMode(FillMode.Solid)
.OptionLabel("Select sub-category...")
.HtmlAttributes(new { style = "width: 100%" })
.DataTextField("Name")
.DataValueField("Id")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetLookupCategoriesSub", "Api")
.Data("filterSubCategories");
})
.ServerFiltering(true);
})
.Enable(false)
.AutoBind(false)
.CascadeFrom("CategoryMainId")
)
</div>
</div>
Script:
@section Scripts {
<script>
function filterMainCategories() {
return {
headId: $("#CategoryHeadId").val()
};
}
function filterSubCategories() {
return {
headId: $("#CategoryHeadId").val(),
mainId: $("#CategoryMainId").val()
};
}
</script>
}
Controller:
public async Task<JsonResult> GetLookupCategoriesHead()
{
var result = await serviceLookUps.GetAllCategoriesHeadAsync();
return new JsonResult(result);
}
public async Task<JsonResult> GetLookupCategoriesMain(int headId)
{
var result = await serviceLookUps.GetAllCategoriesMainAsync(headId);
return new JsonResult(result);
}
public async Task<JsonResult> GetLookupCategoriesSub(int headId, int mainId)
{
var result = await serviceLookUps.GetAllCategoriesSubAsync(headId, mainId);
return new JsonResult(result);
}
Model:
[DebuggerDisplay($"{nameof(Id)}: {{{nameof(Id)},nq}}, {nameof(Name)}: {{{nameof(Name)}}}, {nameof(Active)}: {{{nameof(Active)}}}")]
public class LookupCategoryHeadModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public bool Active { get; set; }
}
[DebuggerDisplay($"{nameof(Id)}: {{{nameof(Id)},nq}}, {nameof(Name)}: {{{nameof(Name)}}}, {nameof(Active)}: {{{nameof(Active)}}}")]
public class LookupCategoryMainModel
{
public int Id { get; set; }
public int HeadId { get; set; }
public string Name { get; set; } = string.Empty;
public bool Active { get; set; }
}
[DebuggerDisplay($"{nameof(Id)}: {{{nameof(Id)},nq}}, {nameof(Name)}: {{{nameof(Name)}}}, {nameof(Active)}: {{{nameof(Active)}}}")]
public class LookupCategorySubModel
{
public int Id { get; set; }
public int HeadId { get; set; }
public int MainId { get; set; }
public string Name { get; set; } = string.Empty;
public bool Active { get; set; }
}
I have tested the API methods separately through tests and ensured they are returning values. The only issue is, when I make a selection in the second dropdown, on the API, the second parameter is received as 0 instead of a correct selected value. This doesn't cause any console errors so the UI continues to work. I can cascade from dropdown one to dropdown two but dropdown two sends a 0 for mainId from its filtering operation in filterSubCategories()
In Kendo UI for ASP.Net MVC, you could bind a grid directly to a DataTable, as in
@Html.Kendo().Grid(MyDataTable)...
where MyDataTable is a DataTable object populated with data.
However, using the ASP.Net Core kendo libraries, there is no support for this.
Why not? Are Telerik planning to add this?
Thanks.
So I just created a .NET 6 ASP.NET Core Web App (Razor Pages), added all telerik stuff according to the instructions (NuGet Packages, scripts and styles in _Layout.cshtml, @addTagHelper-s in _ViewImports.cshtml), and HTMLHelpers are rendering correctly. However, when I provide a data source to the DropDownList or ComboBox, all options show up as undefined.
Telerik version: 2022.2.621. Bootstrap: V5.
Here's the code for DropDownList:
Index.cshtml
@page
@model IndexModel
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@Html.AntiForgeryToken()
@{
ViewData["Title"] = "Home page";
}
--------------------------------------------------------------------------------
<div class="text-center">
<h1 class="display-4">Welcome</h1>
@(Html.Kendo().DropDownList()
.Name("MyDropdownn")
.DataTextField("Text")
.DataValueField("Value")
.HtmlAttributes(new { style = "width:300px;" })
.AutoBind(false)
.Filter(FilterType.Contains)
.DataSource(ds => ds
.Custom()
.Transport(transport => transport
.Read(r => r
.Url("/Index?handler=Sports")
))
.ServerFiltering(false)
)
)
</div>
Index.cshtml.cs
public JsonResult OnGetSports()
{
var allSports1 = db.Sports.Where(x => x.SportID > 0).Select(x => new DropDownModel
{
Text = x.Name,
Value = x.SportID
}).ToList();
return new JsonResult(allSports1);
}
Proof that the data isn't empty/undefined
DropDownModel.cs (DropDownModel class)
public class DropDownModel
{
public int Value { get; set; }
public string Text { get; set; } = String.Empty;
}
I've tried restarting everything, double-checking everything but nothing helped. Is there a bug with this version of telerik, is something incompatible with ,NET 6 or Bootstrap 5? Or am I doing something wrong?