how to map composite key in CRUD functionality - asp.net-mvc-5

I need to map based on two keys(comp and part).
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.comp)
</td>
<td>
#Html.DisplayFor(modelItem => item.part)
</td>
...........................
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.comp }) |
#Html.ActionLink("Details", "Details", new { id = item.comp }) |
#Html.ActionLink("Delete", "Delete", new { id = item.comp })
</td>
</tr>
}
how to use composite key in Index page and also in controller.
public ActionResult Edit(int? id)
{
DataView record = db.RecordDataView.Find(id);
if (record == null)
{
return HttpNotFound();
}
return View(record);
}
If anyone have idea please reply.

The find method, finds entities by the primary key. If you have a composite primary key, then pass the key values in the order they are defined in model:
#Html.ActionLink("Edit", "Edit", new { comp = item.comp, part = item.part }) |
#Html.ActionLink("Details", "Details", new { comp = item.comp, part = item.part }) |
#Html.ActionLink("Delete", "Delete", new { comp = item.comp, part = item.part })
In the controller, receive both values:
public ActionResult Edit(int? comp, int? part)
{
DataView record = db.RecordDataView.Find(comp, part);
if (record == null)
{
return HttpNotFound();
}
return View(record);
}

On applying the above suggestion, I got the error :
The parameters dictionary contains a null entry for parameter 'isdel' of non-nullable type 'System.Boolean' for method 'System.Web.Mvc.ActionResult Edit(System.Nullable`1[System.Int32], Boolean)' in Controller'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
But as per this suggestion
I tried changing Details action signature to
public ActionResult Details(int eid,bool isdeleted) //same parameter name as in my Entity Model.
CRUD functionality with composite keys executes normally now.
Thanks #Jelle Oosterbosch

Related

asp-page-handler helper tag not populating the correct hyperlink

I'm having trouble with asp-page-handler populating the correct link when passing in an route value for an ID and not the handler (so "{int:id}" vs "{handler?}"). (See bottom of image).
I'm expecting something such as:
https://localhost:5001/Emp/Download?id=Matthew.pdf
In my small test app I hardcoded the employee id value into the GetDirectoryReference("E000002/stubs/") and it works fine. (Note that the E000002 is the value that changes dependant upon the logged in person. The value does populate in my OnGetAsync() call so no issue there).
Setup for customer interface GetEmployeePayrollNo:
public async Task<Employee> GetEmployeePayrollNo(int id)
{
return await _context.Employees
.Include(e => e.EmployeePayroll)
.Where(p => p.EmployeeId == id)
.FirstAsync();
}
In this test I'm trying to pass a variable into GetDirectoryReference dependant upon who is logged in.
Not sure what I'm messing up since selecting download or view doesn't even hit on debug mode.
Using Azure File Share to hold documents*
Using Razor pages for folder structure*
Pages
Emp
Paystubs.cshtml
Paystubs.cshtml takes an id route value for the person logged in. In my test application it took a "{handler?}" route value. Not sure if I can use both???
Model
private readonly IConfiguration _configuration;
private readonly ICustomer _customer;
public PaystubsModel(IConfiguration configuration,
ICustomer customer)
{
_configuration = configuration;
_customer = customer;
}
public Employee Employee { get; set; }
public List<AzureFileModel> AzureFileModel { get; private set; } = new List<AzureFileModel>();
public async Task OnGetAsync(int id)
{
Employee = await _customer.GetEmployeePayrollNo(id);
var empPayNo = Employee.EmployeePayroll;
string fileStorageConnection = _configuration.GetValue<string>("FileStorage");
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(fileStorageConnection);
CloudFileShare share = storageAccount.CreateCloudFileClient().GetShareReference("test");
CloudFileDirectory root = share.GetRootDirectoryReference();
CloudFileDirectory dir = root.GetDirectoryReference(empPayNo.EmployeeOnline+"/stubs");
// list all files in the directory
AzureFileModel = await ListSubDir(dir);
}
public static async Task<List<AzureFileModel>> ListSubDir(CloudFileDirectory fileDirectory)
{
// LEFT OUT FOR BREVITY
}
public async Task<IActionResult> OnGetDownload(string fileId)
{
var empPayNo = Employee.EmployeePayroll;
string fileStorageConnection = _configuration.GetValue<string>("FileStorage");
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(fileStorageConnection);
CloudFileShare share = storageAccount.CreateCloudFileClient().GetShareReference("test");
CloudFileDirectory rootDir = share.GetRootDirectoryReference();
CloudFileDirectory dir = rootDir.GetDirectoryReference(empPayNo.EmployeeOnline+"/stubs");
CloudFile file = dir.GetFileReference(fileId);
if (!file.Exists())
{
ModelState.AddModelError(string.Empty, "File not found.");
return Page();
}
else
{
await file.DownloadToStreamAsync(new MemoryStream());
Stream fileStream = await file.OpenReadAsync();
return File(fileStream, file.Properties.ContentType, file.Name);
}
}
Page:
#page "{id:int}" //*******In my test model I was using {handler?} but I need to pass in the employee id to route here to display only the logged in employee. Again, not sure if its possible to use both?
#model NavraePortal.WebApp.Pages.Emp.PaystubsModel
#{
ViewData["Title"] = "Documents";
}
<h1>Pay Stub Copies</h1>
<table class="table table-bordered">
<thead>
<tr>
<th>File Name</th>
<th>File Date</th>
<th>Download</th>
<th>View</th>
</tr>
</thead>
<tbody>
#foreach (var data in Model.AzureFileModel)
{
<tr>
<td>#data.FileName</td>
<td>#data.DateModified</td>
<td>
<a class="btn btn-primary" asp-route-id="#data.FileName" asp-page-handler="Download">Download</a>
</td>
<td>
<a class="btn btn-info" asp-route-id="#data.FileName" asp-page-handler="View">View</a>
</td>
</tr>
}
</tbody>
</table>
Because this id in asp-route-id is not matched with the fileId. In this page, you need to modify it.
<a class="btn btn-primary" asp-route-fileId="#data.FileName" asp-page-handler="Download">Download</a>
Then, this url is updated.

Can you use IAsyncEnumerable in Razor pages to progressively display markup?

I've been playing around with Blazor and the IAsyncEnumerable feature in C# 8.0. Is it possible to use IAsyncEnumerable and await within Razor Pages to progressively display markup with data?
Example service:
private static readonly string[] games = new[] { "Call of Duty", "Legend of Zelda", "Super Mario 64" };
public async IAsyncEnumerable<string> GetGames()
{
foreach (var game in games)
{
await Task.Delay(1000);
yield return game;
}
}
Example in razor page:
#await foreach(var game in GameService.GetGames())
{
<p>#game</p>
}
This gives error CS4033: The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
Any ideas if this is possible?
Server-side Razor allows what you describe. This video describes the code in this Github repo that shows how to use IAsyncEnumerable by modifying the ForecastService example in server-side Blazor template.
Modifying the service itself is easy, and actually results in cleaner code :
public async IAsyncEnumerable<WeatherForecast> GetForecastAsync(DateTime startDate)
{
var rng = new Random();
for(int i=0;i<5;i++)
{
await Task.Delay(200);
yield return new WeatherForecast
{
Date = startDate.AddDays(i),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
};
}
}
The Blazor page on the other hand is more complicated. It's not just that the loop would have to finish before the HTML was displayed, you can't use await foreach in the page itself because it's not asynchronous. You can only define asynchronous methods in the code block.
What you can do, is enumerate the IAsyncEnumerable and notify the page to refresh itself after each change.
The rendering code itself doesn't need to change :
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
#foreach (var forecast in forecasts)
{
<tr>
<td>#forecast.Date.ToShortDateString()</td>
<td>#forecast.TemperatureC</td>
<td>#forecast.TemperatureF</td>
<td>#forecast.Summary</td>
</tr>
}
</tbody>
</table>
OnInitializedAsync needs to call StateHasChanged() after receiving each item :
List<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
forecasts =new List<WeatherForecast>();
await foreach(var forecast in ForecastService.GetForecastAsync(DateTime.Now))
{
forecasts.Add(forecast);
this.StateHasChanged();
}
}
In the question's example, incoming games could be stored in a List, leaving the rendering code unchanged :
#foreach(var game in games)
{
<p>#game</p>
}
#code {
List<string> games;
protected override async Task OnInitializedAsync()
{
games =new List<games>();
await foreach(var game in GameService.GetGamesAsync())
{
games.Add(game);
this.StateHasChanged();
}
}
}
You can't write await foreach on .razor template code. But, as workaround, you can write it at #code section:
#if (#gamesUI == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Game</th>
</tr>
</thead>
<tbody>
#foreach (var game in gamesUI) // <--- workaround
{
<tr>
<td>#game</td>
</tr>
}
</tbody>
</table>
}
#code {
List<string> gamesUI; // <--- workaround
protected override async Task OnInitializedAsync()
{
gamesUI = new List<string>();
await foreach(var game in GService.GetgamesAsync() )
{
gamesUI.Add(game);
this.StateHasChanged();
}
}
}
Effect:
Yielding data:
private static readonly string[] games = new[] { "Call of Duty", "Legend of Zelda", "Super Mario 64", "Bag man" };
public async IAsyncEnumerable<string> GetgamesAsync()
{
var rng = new Random();
foreach (var game in games)
{
await Task.Delay(1000);
yield return game;
}
}

Asp.net MVC 5 passing model object to controller via ActionLink

I need some help/advise on how to make this work.
I need to pass the model from the view to the controller through an ActionLink
#Html.ActionLink("Radera", "DeleteTraffic", new { model = Model, trafficId = traffic.Id }, new { #class = "btn btn-link NoBorder NoBackGround" })
the method in the controller looks like this.
public ActionResult DeleteTraffic(CalendarModel model, int trafficId)
{
return View("EditDay", model);
}
I have not put any code in the method yet, I've only been debugging it to get the call to work. model is null when I press the button, trafficId is however correctly set. so what have I done wrong?
Edit 1:
I've changed the code according to the suggestions here.
#using (Html.BeginForm("DeleteTraffic", "Calendar", new {trafficId = traffic.Id})) {<input type="submit" value="Radera" class="btn btn-link NoBorder NoBackGround"/>}
[HttpPost]
[ValidateAntiForgeryToken]
[ActionName("DeleteTraffic")]
public ActionResult DeleteTraffic(int trafficId)
{
return View("EditDay", Model);
}
but DeleteTraffic is never reched, instead it calls the Main Action for this page.
// GET: Calendar
public ActionResult Calendar()
{
CalendarModel model = new CalendarModel {SelectedDate = DateTime.Today};
if (Request.HttpMethod == "POST")
{
if (!string.IsNullOrEmpty(Request.Form.Get("submit.SelectDate")))
{
model.SelectedDate = Convert.ToDateTime(Request.Form["selectedDate"]);
model.TrafficDates = TrafficData.GeTrafficDatesPerMonth(model.SelectedDate);
Model = model;
return View("EditDay", Model);
}
}
Model = model;
return View(Model);
}
should I just tuck the trafficId into a hiddenfield and use this action for everything? MVC seems so inflexible at times...
First, something like a "delete" should never be handled by GET. Deleting is atomic and should be done utilizing either the POST or DELETE (preferably) verbs. Generally, you also should not just delete something without user confirmation, so the simplest and correct way to handle this would be to have the "delete" link take the user to a view that asks them to confirm deleting the item. On this view, then, you would submit the id of the item to be deleted via a form post:
public ActionResult Delete(int id)
{
var foo = db.Foos.Find(id);
if (foo == null)
{
return new HttpNotFoundResult();
}
return View(foo);
}
[HttpPost]
[ValidateAntiForgeryToken]
[ActionName("Delete")]
public ActionResult DeleteConfirm(int id)
{
var foo = db.Foos.Find(id);
if (foo == null)
{
return new HttpNotFoundResult();
}
db.Foos.Remove(foo);
db.SaveChanges();
return RedirectToAction("Index");
}
Then, for your GET action, you would add a Delete.cshtml file:
#model Namespace.To.Foo
<p>Are you sure you want to delete the foo, "#Model.Name"?</p>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Id)
#Html.ActionLink("Cancel", "Index")
<button type="submit">Delete</button>
}
Alternatively (or rather progressively, as you should still have the previous method as a fallback), you could use a JavaScript confirm and AJAX to do this, if you don't want to change pages:
#Html.ActionLink("Radera", "DeleteTraffic", new { id = item.Id }, new { #class = "btn btn-link NoBorder NoBackGround delete", data_id = item.Id })
Then:
<script>
$('.delete').on('click', function () {
var $deleteLink = $(this);
if (confirm('Are you sure?')) {
$.post('/url/for/delete/', { id = $deleteLink.data('id') }, function () {
$deleteLink.closest('tr').remove();
});
}
});
</script>

Writing a Content Part that contains a list of content parts

I've been trying to write a simple accordian widget, where each section of accordian would be its own content part. I have the leafs content part created fine, but I want to create the accordian part which contains a list of the leafs. I havn't been able to find a good tutorial that went over something like this. I'm working on displaying the leafs now, and am running into issues. I'm trying to mimic the comments module. This is what I have. It seems like i am able to get up to the list of leafs in the driver, but i'm not sure what to do with the view. i see comments calls #Display(Model.List) but i have no idea what this is doing.
Edit View (Just using a textbox for one leaf id, need to figure out how to select leaves):
<fieldset>
<legend>Accordian Fields</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AccordianLeaf)
</div>
<div class="editor-field">
#*#Html.DropDownListFor(m => m.AccordianLeaf,
new System.Web.Mvc.SelectList(service.GetComments(), "Value", "Text"))*#
#Html.TextBoxFor(model => model.AccordianLeaf)
#Html.ValidationMessageFor(model => model.AccordianLeaf)
</div>
</fieldset>
Model:
namespace SuccessCenter.Models
{
public class AccordianRecord : ContentPartRecord
{
public virtual string Title { get; set; }
public virtual int AccordianLeaf { get; set; }
}
public class AccordianPart : ContentPart<AccordianRecord>
{
[Required]
public string Title
{
get { return Retrieve(r => r.Title); }
set { Store(r => r.Title, value); }
}
[Required]
public int AccordianLeaf
{
get { return Retrieve(r => r.AccordianLeaf); }
set { Store(r => r.AccordianLeaf, value); }
}
}
}
Handler:
namespace SuccessCenter.Handlers
{
public class AccordianHandler : ContentHandler
{
public AccordianHandler(IRepository<AccordianRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
}
Driver:
namespace SuccessCenter.Drivers
{
[UsedImplicitly]
public class AccordianDriver : ContentPartDriver<AccordianPart>
{
private readonly IAccordian _accordian;
public AccordianDriver(IAccordian accordian)
{
_accordian = accordian;
}
protected override DriverResult Display(AccordianPart part, string displayType, dynamic shapeHelper)
{
//return ContentShape("Parts_Accordian", () => shapeHelper.Parts_Accordian(Title: part.Title, AccordianLeaf: part.AccordianLeaf));
return Combined(
ContentShape("Parts_Accordian",
() =>
{
// create a hierarchy of shapes
var firstLevelShapes = new List<dynamic>();
var allShapes = new Dictionary<int, dynamic>();
var AccordianLeafs = _accordian.AccordianLeafs.ToList();
foreach (var item in AccordianLeafs)
{
var shape = shapeHelper.AccordianLeaf(ContentPart: item, ContentItem: item.ContentItem);
allShapes.Add(item.Id, shape);
}
var list = shapeHelper.List(Items: allShapes);
return shapeHelper.Parts_Accordian(
List: list
);
}));
}
//GET
protected override DriverResult Editor(AccordianPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Accordian_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Accordian", Model: part, Prefix: Prefix));
}
//POST
protected override DriverResult Editor(AccordianPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
}
View:
#using SuccessCenter.Models;
}<div class="expand-view expanded">
<header class="bg-brand-blue txt-white relative">
<h3 class="txt-left">#Model.List.Title</h3>
<span class="toggle v-align absolute">
<span class="expanded">Colllapse <i class="icons icon-carat-up-wh"></i></span><span class="collapsed">Expand <i class="icons icon-carat-down-wh"></i></span>
</span>
</header>
<section class="default-padding">
#Model.List.AccordianLeaf
</section>
##Display(Model.List)#
The List shape (shapeHelper.List()) takes a range of content item shapes, which you seem to build with shapeHelper.AccordionLeaf(). Therefore you can just display it with the Display method:
#Display(Model.List)
This method will display the List property on your model (your model properties are the ones you give as parameter in shapeHelper.Parts_Accordian(/* model properties */)
I am not sure what you are trying to do in the view, it seems like your want to iterate over the items in the List shape? In that case you can do something like this:
#foreach (var item in Model.List.Items) {
// item here is an AccordionLeaf shape
#Display(item)
}

How to get values from assigned contentpart in Orchard CMS

I'm using Orchard CMS v1.8.x. We've added the FlexSlider module and to make it link to specific areas of our site, we've added a field called "Slide Link" to the FlexSliderPart object.
Now, that all works pretty neat. But I have absolutely no idea how to reference this new field on the front-end. The FlexSliderWidgetViewModel only contains fields for Title and ImagePath. I have no idea how to retrieve the SlideLink (and SubTitle) field:
namespace Tekno.FlexSlider.ViewModels
{
public class FlexSliderWidgetViewModel
{
public string Title { get; set; }
public string ImagePath { get; set; }
}
}
And my View:
#using Tekno.FlexSlider.ViewModels
#{
Style.Require("FlexSlider");
Script.Require("FlexSlider");
var items = (IEnumerable<FlexSliderWidgetViewModel>)Model.SlideItems;
}
<div class="flexslider">
<ul class="slides">
#foreach (var item in items)
{
<li>
#if (item.ImagePath != "")
{
<img src="#Display.ResizeMediaUrl(Path: item.ImagePath, Height: 400, Mode: "crop")" />
}
<span class="slide-text">#item.Title</span>
</li>
}
</ul>
<span class="slide-text-bottom"></span>
</div>
The Driver's Display function:
protected override DriverResult Display(FlexSliderWidgetPart part, string displayType, dynamic shapeHelper)
{
var items = _contentManager.Query<FlexSliderPart, FlexSliderPartRecord>("FlexSlider")
.Where(i => i.GroupId == part.GroupId).OrderBy(i => i.Sort)
.List()
.Select(i => new FlexSliderWidgetViewModel()
{
ImagePath = ((MediaLibraryPickerField)i.Fields.Single(f => f.Name == "Picture"))
.MediaParts
.FirstOrDefault() == null ? "" : ((MediaLibraryPickerField)i.Fields.Single(f => f.Name == "Picture")).MediaParts.First().MediaUrl,
Title = i.Get<TitlePart>().Title
});
return ContentShape("Parts_FlexSliderWidget",
() => shapeHelper.Parts_FlexSliderWidget(SlideItems: items));
}
I had a quick look at the code and you won't be able to access those fields in the way the module is currently working. Basically it is accessing the content item in the drivers display method and then creating a View Model with the two bits of data (title and image)it deems necessary to send to the view. I would recommend changing the drivers Display method to send back the entire content item instead of that view model, you can then access fields you attach from the view directly.
If you don't have access to the driver you could I suppose grab the content manager in your view and redo all the work that driver is doing so you can access the content items. I wouldn't recommend this approach though...
This probably isn't the answer you were hoping for, sorry.
EDIT
This is basically pseudo code as I don't have access to the module to actually see if it works, but it should point you in, well, some sort of direction.
protected override DriverResult Display(FlexSliderWidgetPart part, string displayType, dynamic shapeHelper)
{
var items = _contentManager.Query<FlexSliderPart, FlexSliderPartRecord>("FlexSlider")
.Where(i => i.GroupId == part.GroupId).OrderBy(i => i.Sort)
.List();
return ContentShape("Parts_FlexSliderWidget",
() => shapeHelper.Parts_FlexSliderWidget(SlideItems: items));
}
then in your view:
#using Tekno.FlexSlider.ViewModels
#{
Style.Require("FlexSlider");
Script.Require("FlexSlider");
}
<div class="flexslider">
<ul class="slides">
#foreach (var part in Model.SlideItems)
{
dynamic item = part.ContentItem;
<li>
<img src="#Display.ResizeMediaUrl(Path: item.FlexSliderPart.MediaParts[0].MediaUrl, Height: 400, Mode: "crop")" />
<span class="slide-text">#item.TitlePart.Title</span>
</li>
}
</ul>
<span class="slide-text-bottom"></span>
</div>
Important thing here is the casting the content item to a dynamic so you can access all the fields etc. I've also never used the new Media stuff in 1.8 so... don't know if I'm accessing that correctly. I don't like it ^^

Resources