.net mvc declare a property to hold a collection of generic objects? - c#

I have an MVC proyect using EF (database first) and I have already created CRUD for some entities.
Now I am trying to create a dashboard page that contains widgets or similar, each listing the last 10 entities from different db tables (last 10 products created, last 10 customers, etc)
To create the widget I have followed this tutorial https://www.codeproject.com/Articles/598383/Widgets-in-MVC
So I have 2 interfaces and 2 classes that implements those interfaces:
public interface IWidget
{
int SortOrder { get; set; }
string Entity { get; set; }
string CssClassName { get; set; }
string HeaderText { get; set; }
string FooterText { get; set; }
ISubWidget SubWidget { get; set; }
}
public interface ISubWidget
{
ICollection<Products> EntitiesList { get; set; }
}
public class Widget : IWidget
{
public int SortOrder { get; set; }
public string Entity { get; set; }
public string HeaderText { get; set; }
public string FooterText { get; set; }
public ISubWidget SubWidget { get; set; }
}
public class SubWidget : ISubWidget
{
public ICollection<Products> EntitiesList { get; set; }
}
then I have 3 partial views: one for the widget itself, one for the subwidget, and one that will act as a container of all created widgets.
#model Proyect.Comun.Widget.IWidget
<div class="widget">
<div class="#Model.CssClassName">
<div class="widgetHeader">
#Model.HeaderText
</div>
<div class="widgetBody">
#Html.Partial(string.Concat(new[] { "Widget", "/", "_SubWidget" }), #Model.SubWidget)
</div>
<div class="widgetFooter">
#Model.FooterText
</div>
</div>
</div>
#model Proyect.Comun.Widget.ISubWidget
<table>
#foreach (var item in Model.EntitiesList)
{
<tr>
<td> #Html.DisplayFor(modelItem => item.product_number) </td>
<td> #Html.DisplayFor(modelItem => item.product_name) </td>
<td> #Html.DisplayFor(modelItem => item.product_description) </td>
</tr>
}
</table>
#model Fruterias.Comun.Widget.IWidget
#foreach (Proyect.Comun.Widget.IWidget wm in ViewBag.Widgets)
{
#Html.Partial(string.Concat(new[] { "Widget", "/", "_Widget" }), wm)
}
then in the Dashboard controller:
public ActionResult Index()
{
ViewBag.Widgets = GetWidgetData();
return View();
}
public List<IWidget> GetWidgetData()
{
var lastWidget = new List<IWidget>{
new Widget()
{
SortOrder = 1,
CssClassName = "high",
Entity = "Products",
HeaderText = "Last Products",
FooterText = "",
SubWidget = new SubWidget {
EntitiesList = db.products.OrderByDescending(p => p.date).Take(10).ToList(),
}
},
};
return lastWidget;
}
And finally in the view for Dashboard/Index:
<p>
#{
#Html.Partial("Widget/_WidgetContainer");
}
</p>
This works ok and shows a list of Product entities. But now I want to create different widgets associated to different entities.
Of course the problem is that the property EntitiesList is declared as a collection of Product objects, so I can not fill it with, for example, customers objects.
What would be the solution here? I could create different interfaces (and classes) for each type of entity, but Im sure there must be a better way...
Also, as the entities models (Products, Customers...) are generated with Entity Framework db first, I guess I can not create an Interface and make them implement that interface (or can I?)... (anytime the db changes and models are regenerated by EF, all that would be lost?)

If you don't need to have the EntitiesList to be a specific type you could just have the SubWidget be generic and set its type when you instantiate.
public class SubWidget<T> : ISubWidget<T> where T : class
{
public ICollection<T> EntitiesList { get; set; }
}
or you could use dynamic object.
public class SubWidget : ISubWidget
{
public ICollection<dynamic> EntitiesList { get; set; }
}

Related

Deriving calculated values and assigning them to variables using values stored in database tables using ASP.NET, EF, C#

I've created an ASP.NET MVC CRUD site for entering numerical data into fields that are then added to the db which are raw test results. I have a pretty large table storing each individual raw test value which are weights and lbs of force. The problem I'm trying to noodle through is now that I have the raw data in the db, I need to pull values and do math on them in order to present summary data in another view.
For example, I have 6 columns:
hardness1_before
hardness2_before
hardness3_before
hardness1_after
hardness2_after
hardness3_after
These values are stored in the database. I need to calculate the average of all 3 hardness_before values, all 3 hardness_after values and then output the difference:
changeInHardness = (Avg_hardness_after) - (Avg_hardness_before)
At this point, I've figured out to do this in the html view itself, but I'm thinking there has got to be a better way using the viewmodel and controller? Here is my code and workflow below. I feel this solution is not ideal coding as shouldn't be we only passing data to views rather than using the view to calculate? Also, I wish to convert this data into a bar graph so I'm suspecting that I'll need publicly available values to do so.
Any thoughts and insight on how I might go about cleaning this up would be greatly appreciated.
Model classes:
public class Test
{
public int TestID { get; set; }
public int CustomerID { get; set; } //foreign key
[Display(Name = "Contact")]
public string? ContactName { get; set; }
[Required]
[Display(Name ="Mud Type")]
public string? MudType { get; set; }
public Customer? Customer { get; set; }
public ICollection<TestResults>?Results { get; set; }
}
public class TestResults
{
public int TestResultsID { get; set; } //primary key
public int TestID { get; set; } //foreign key
[Display(Name = "Hardness After")]
[Required]
public double S1Hardness_a { get; set; }
[Display(Name = "Hardness Before")]
[Required]
public double S1Hardness_b { get; set; }
[Display(Name = "Hardness After")]
[Required]
public double S2Hardness_a { get; set; }
[Display(Name = "Hardness Before")]
[Required]
public double S2Hardness_b { get; set; }
[Display(Name = "Hardness After")]
[Required]
public double S3Hardness_a { get; set; }
[Display(Name = "Hardness Before")]
[Required]
public double S3Hardness_b { get; set; }
public Test Test { get; set; } //nav prop
}
I'm using a ViewModel as the results returns many test results per test
public class ReportsViewModel
{
public Test TestDVm { get; set; } //1 test
public IEnumerable<TestResults>? TestResultsDVm { get; set; } //multiple tests
}
The controller:
public class ReportsController : Controller
{
private readonly MudTestAppContext _context;
public ReportsController(MudTestAppContext context)
{
_context = context;
}
public async Task<IActionResult> Index(int? id) //id = test ID
{
if (id == null)
{
return NotFound();
}
viewModel.TestDVm = await _context.Tests
.Include(i => i.Results)
.FirstOrDefaultAsync(t => t.TestID == id);
if (viewModel == null)
{
return NotFound();
}
return View(viewModel);
}
}
The view currently looks like this:
#model MudTestApp.Models.TestViewModels.ReportsViewModel
#{
ViewData["Title"] = "Report";
}
<div>
<h4>Details for Test Number: #Model.TestDVm.TestID</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
#Html.DisplayNameFor(model => model.TestDVm.Customer.CompanyName)
</dt>
<dd class = "col-sm-10">
#Html.DisplayFor(model => model.TestDVm.Customer.CompanyName)
</dd>
<dt class = "col-sm-2">
#Html.DisplayNameFor(model => model.TestDVm.ContactName)
</dt>
<dd class = "col-sm-10">
#Html.DisplayFor(model => model.TestDVm.ContactName)
</dd>
</dl>
<div>
<a asp-controller="Tests" asp-action="Index">Back to List</a>
</div>
<h2>Test Results Summary</h2>
<div>
<table class="table">
<th>Compound</th>
<th>Test Temp</th>
<th>Change in Hardness</th>
#foreach (var item in Model.TestDVm.Results)
{
var AvgHb = ((item.S1Hardness_b + item.S2Hardness_b + item.S3Hardness_b) / 3);
var AvgHa = ((item.S1Hardness_a + item.S2Hardness_a + item.S3Hardness_a) / 3);
var AvgHard = AvgHa - AvgHb;
<tr>
<td>#Html.DisplayFor(modelItem => item.TestTemp)</td>
<td>#AvgHard</td>
</tr>
}
</table>
</div>
</table>

Net Core 3.0: multiple checkboxes with integer variables

In my web app I have a self referencing many to many relationship of plants. The M2M relationships are good neighbours and bad neighbours. I want to be able to have a form where a user can check off the both types of neighbours and then save the form.
What I have so far:
For brevity, I will only show code to Good neighbours relation, the bad neighbours is the same.
Models
public class Plant
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<GoodPlants> GoodNeighbours { get; set; }
}
public class GoodPlants
{
public int PlantId { get; set; }
public int GoodNeighbourId { get; set; }
public virtual Plant Plant { get; set; }
public virtual Plant GoodNeighbour {get; set;}
}
My viewmodel EditViewModel:
public class EditViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<PlantIsSelectedViewModel> AllPlants { get; set; }
}
And the model PlantIsSelected
public class PlantIsSelectedViewModel
{
public int id { get; set; }
public string name { get; set; }
public bool isGoodNeighbour { get; set; }
public bool isBadNeighbour { get; set; }
}
I have an Edit method to display the edited plant and all the other plants:
var plant = await _context.Plants.FindAsync(id);
ICollection<Plant> plants = _context.Plants.ToList();
ICollection<GoodPlants> goodNeighbours = await _context.GoodNeighbours
.Where(g => g.PlantId == id)
.Include(g => g.GoodNeighbour)
.ToListAsync();
GoodPlants ownGoodPlant = goodNeighbours.FirstOrDefault(i => i.GoodNeighbour.Id == plant.Id);
Plant ownPlant = plants.FirstOrDefault(i => i.Id == plant.Id);
goodNeighbours.Remove(ownGoodPlant);
plants.Remove(ownPlant);
//populate the viewmodel
EditViewModel plantModel = new EditViewModel();
plantModel.Id = plant.Id;
plantModel.Name = plant.Name;
plantModel.AllPlants = _mapper.Map<ICollection<PlantIsSelectedViewModel>>(Plants);
foreach (var element in plantModel.AllPlants)
{
if (goodNeighbours.Any(g => g.GoodNeighbour.Id == element.id))
{
element.isGoodNeighbour = true;
}
else if (badNeighbours.Any(g => g.BadNeighbour.Id == element.id))
{
element.isBadNeighbour = true;
}
}
This desperately needs refactoring, but thats not the main issue here.
In my view I contrast the elements of AllPlants collection if it appears on either of the neighbours collections and have the checkbox set to checked or not:
<tbody>
#foreach (var item in Model.AllPlants)
{
<tr>
#if (item.isGoodNeighbour)
{
<td>
#Html.Label(item.name)
</td>
<td>
<input type="checkbox" name="#Model.AllPlants"
value="#item.id"
#(Html.Raw("checked=\"checked\""))
/>
</td>
<td>
<input type="checkbox" name="#Model.AllPlants"
value="#item.id" />
</td>
}
// else statements below for badneighbours and cases without any relation.
// ...
</tr>
}
I want to know how I can keep track of all the selected items (and unselected), get them in my editViewModel and send them back to my [HttpPost] Edit method. My current [HttpPost] method receives the same viewmodel, but the AllPlants property is empty.
How do I receive the correct data back?
Thanks in advance, I'm pretty stuck!

Passing html fileupload value to a c# method in MVC 5 to bind to a partial view

I am new to MVC (coming from WebForm background). I have a main view which contains 2 tabs that need the same model for table view. However, I created a partial view containing table structure to display data records in the 2 tabs asynchronously. One tab gets its data from CSV while the other gets from SQL Server based on user's selection but having the same model.
I have the screenshot of the concept below: The box in red depicts the Partial View, it has 2 buttons, one to commit the table data into the database while the other is to add more records if needed;
Partial view in Parent View screenshot
My challenge is: I have a class method that needs to get a CSV file from FileUpload value and binds data to the model on the partial view.
See my model structure below:
[Table("atm")]
public class ATM
{
public ATM()
{
this._EJTransactions = new HashSet<EJTransaction>();
this._CassetteSettings = new HashSet<CassetteSetting>();
this._EJFileDownloads = new HashSet<EJFileDownload>();
this._CamFileDownloads = new HashSet<ImageFileDownload>();
this._TransImages = new HashSet<TransImage>();
this._UserAtms = new HashSet<UserAtms>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int atmId { get; set; }
[ForeignKey("ATMVendor")]
public int vendorId { get; set; }
[ForeignKey("BankBranch")]
public int branchId { get; set; }
[MaxLength(50)]
[Index("ix_uniq_terminal", 1, IsUnique = true)]
[DisplayName("Terminal ID")]
public string terminalId { get; set; }
[MaxLength(30)]
[Index("ix_uniq_ip", 1, IsUnique = true)]
[DisplayName("IP")]
public string ip { get; set; }
[MaxLength(100)]
[Index("ix_uniq_title", 1, IsUnique = true)]
[DisplayName("Title")]
public string title { get; set; }
[DisplayName("EJ Enabled")]
public bool ejEnabled { get; set; }
[DisplayName("Image Enabled")]
public bool camEnabled { get; set; }
[DisplayName("IsActive")]
public bool isActive { get; set; }
public virtual ATMVendor ATMVendor { get; set; }
public virtual BankBranch BankBranch { get; set; }
public virtual ICollection<EJTransaction> _EJTransactions { get; set; }
public virtual ICollection<CassetteSetting> _CassetteSettings { get; set; }
public virtual ICollection<EJFileDownload> _EJFileDownloads { get; set; }
public virtual ICollection<ImageFileDownload> _CamFileDownloads { get; set; }
public virtual ICollection<TransImage> _TransImages { get; set; }
public virtual ICollection<UserAtms> _UserAtms { get; set; }
}
The partial view binds to this model.
public class CSVAtmLoader
{
public IEnumerable<ATM> Read(string csvfile)
{
List<ATM> atmlist = new List<ATM>();
TextReader csvReader = new StreamReader(csvfile);
var csv = new CsvReader(csvReader, false);
csv.Configuration.DetectColumnCountChanges = true;
csv.Configuration.RegisterClassMap<AtmMap>();
//csv.Configuration.InjectionCharacters = new[] { '=', '#', '+', '-' };
//csv.Configuration.SanitizeForInjection = false;
//csv.Configuration.InjectionEscapeCharacter = '\t';
var atms = csv.GetRecords<ATM>();
foreach (var atm in atms)
{
atm.branchId = GetBranchId(atm.BankBranch.branch);
atm.vendorId = GetVendorId(atm.ATMVendor.vendor);
atmlist.Add(atm);
}
return atmlist;
}
private int GetBranchId(string branch)
{
BankBranch br = new BankBranch { branch = branch }.SelectBranch();
return 0;
}
private int GetVendorId(string vendor)
{
return 0;
}
}
In the parent view, I have a CSVAtm tab that hosts the partial view, I am headway blocked to get this done with dynamism of the layout. See my parent view that render the partial view with csvfile chosen from FileUpload control:
<div class="tab-pane fade" id="csvAtm">
<p>
<div class="form-inline">
<table>
<tr>
<td><span> Choose a File:</span> </td>
<td><input id="csvfile" type="file" name="file" class="btn btn-default" onchange="clear();"/></td>
<td> </td>
<td>
<input class="btn btn-default col-mid-2" type="button" id="upload" value="Upload" onclick="uploadfile();" />
</td>
</tr>
</table>
</div>
<div id="error"></div>
<br />
<div class="form-group">
#{
if (string.IsNullOrEmpty(filename))
{
//this is where my challenge is, how to get the filename from fileupload
//I know this is executed at the server side and hence, the client is not aware of server side scripting
Html.RenderPartial("LoadedAtms", new CSVAtmLoader().Read(filename));
}
}
</div>
I need a better way to achieve this with fileupload security in mind, my approach might be wrong. If I can achieve this successfully in the csv tab, it will be easier to replicate same for connecting to Sql server.
Thanks so much..

How to Achieve model binding for Complex types

My model is :
public class ContactInfo
{
public IEnumerable<SupplierContact> PriceRequest { get; set; }
public IEnumerable<SupplierContact> OrderConfirmation { get; set; }
public IEnumerable<SupplierContact> Account { get; set; }
}
public class SupplierContact
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Title { get; set; }
public string Email { get; set; }
public string MobilePhone { get; set; }
}
and my Controller action is
public ActionResult EditContactInfo(ContactInfo contactInfo)
{
// not getting any values here..
}
View is rendering like :
#foreach (SupplierContact PriceRequest in Model.PriceRequest)
{
<tr class="">
<td style="text-align: left;" class="csstd-left">#Html.TextBoxFor(m => PriceRequest.Email)</td>
<td class="csstd">#Html.TextBoxFor(m => PriceRequest.MobilePhone)</td>
<td class="csstd">#PriceRequest.Title</td>
<td class="csstd">#PriceRequest.FirstName</td>
<td class="csstd">#PriceRequest.LastName</td>
</tr>
}
And I am referencing #model ContactInfo in my view
However i can achieve it using
Request.Form.Get("PriceRequest.Email")
but I want to use model binding feature .
You need to use a for loop (and you will need to change the collections from IEnumerable to IList to the name attributes are correctly indexed
#for (int i = 0; i < Model.PriceRequest.Count; i++) {
#Html.TextBoxFor(m => Model.PriceRequest[0].Email)
#Html.TextBoxFor(m => Model.PriceRequest[i].MobilePhone)
}
Alternatively you can create a EditorTemplate for SupplierContact and use
#Html.EditorFor(m => m.PriceRequest)
This will generate html like
<input name="PriceRequest[0].Email" ...
<input name="PriceRequest[0].MobilePhone" ...
<input name="PriceRequest[1].Email" ...
<input name="PriceRequest[2].MobilePhone" ...
etc.
Take a look at display and editor templates. Than you can create a view called SupplierContact. MVC automatically knows what to show if he see the complex type.
See this example:
http://www.asp.net/mvc/tutorials/javascript/using-the-html5-and-jquery-ui-datepicker-popup-calendar-with-aspnet-mvc/using-the-html5-and-jquery-ui-datepicker-popup-calendar-with-aspnet-mvc-part-2
So create a folder: DisplayTemplates in your views folder.
Then create a partial view called SupplierContact.
Set the model of the partial view as a SupplierContact.
Create the labels for displaying and run your application again.
For editing, create a EditorTemplates folder.

asp.net mvc binding between the model and the view

I am new to asp.net mvc . This is how my model looks like:
[Bind(Exclude = "JournalId")]
public class Journal
{
[ScaffoldColumn(false)]
public int JournalId { get; set; }
[DisplayName("Customer")]
public int CustomerId { get; set; }
[DisplayName("Till")]
public int TillId { get; set; }
[Required(ErrorMessage = "A Journal name is required")]
[StringLength(160)]
public string Name { get; set; }
[DisplayName("Journal creation date")]
public DateTime Date { get; set; }
[DisplayName("Journal creation time")]
public DateTime Time { get; set; }
public virtual Customer Customer { get; set; }
public virtual Till Till { get; set; }
}
[Bind(Exclude = "CustomerId")]
public class Customer
{
[ScaffoldColumn(false)]
public int CustomerId { get; set; }
[Required(ErrorMessage = "A customer name is required")]
[StringLength(160)]
public string Name { get; set; }
[StringLength(250)]
public string Address { get; set; }
}
[Bind(Exclude = "TillId")]
public class Till
{
[ScaffoldColumn(false)]
public int TillId { get; set; }
[Required(ErrorMessage = "A till no is required")]
[StringLength(160)]
public string TillNo { get; set; }
[StringLength(100)]
public string TillOperator { get; set; }
}
This is how my one of my controller's action is defined:
public ViewResult Index()
{
var journals = db.Journals.AsEnumerable<Journal>();
ViewData["journals"] = journals;
return View();
}
and the view :
#model IEnumerable<ErikDemo.Models.Journal>
#foreach (var item in (IEnumerable<ErikDemo.Models.Journal>)ViewData["journals"]) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Customer.Name)
</td>
<td>
#Truncate(item.Till.TillNo, 25)
</td>
<td>
#Truncate(item.Name, 25)
</td>
<td>
#Html.DisplayFor(modelItem => item.Date)
</td>
<td>
#Html.DisplayFor(modelItem => item.Time)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.JournalId }) |
#Html.ActionLink("Details", "Details", new { id=item.JournalId }) |
#Html.ActionLink("Delete", "Delete", new { id=item.JournalId })
</td>
</tr>
Although when I am debugging I can see in the controller that the list passed to the View is not empty, and also I see that the ViewData["journals"].Local in a watch is not empty, nothing gets displayed. I have also used the View.Model and return View(journals.ToList()) to send data to the View, but nothing seems to work. What is the issue here? Been on that half a day.
This is wrong: (Well it can be done like this, but I think you want to pass a model)
public ViewResult Index()
{
var journals = db.Journals.AsEnumerable<Journal>();
ViewData["journals"] = journals;
return View();
}
Try this:
public ViewResult Index()
{
var journals = db.Journals.AsEnumerable<Journal>();
return View(journals); //You just passed journals as a model
}
Also if you are using mvc 3 you can use ViewBag instead of ViewData
Example:
ViewData["Journal"] = "my string";
is the same as
ViewBag.Journal = "my string";
The ViewBag is dynamic, so you can use dot notation.
Additionally
This code:
#model IEnumerable<ErikDemo.Models.Journal>
#foreach (var item in (IEnumerable<ErikDemo.Models.Journal>)ViewData["journals"])
Should be like this:
#model IEnumerable<ErikDemo.Models.Journal>
#foreach (var item in Model)
Update:
I'm not sure what you're doing with this db.Journals.AsEnumerable<Journal>();
You should have a method somewhere that gets data from a table or table(s) and returns Journals. So lets say this all comes from one table in a database:
public class JournalViewModel
{
public IEnumerable<Journals> GetJournals()
{
using(var db = new ErikDataContext())
{
return db.Journals.ToList();
}
}
}
Then in the action:
public ViewResult Index()
{
var journals = new JournalsViewModel.GetJournals();
return View(journals); //You just passed journals as a model
}
Did you forget the <table> tag? If you haven't viewed the source of your page as it is rendered, I would recommend that you do this as a next step.

Categories