I've got a simple HomeController with one overloaded (GET/POST) Index method which displays data from a list of Person objects in an HTML table and gives the user a form in which they can type a sheet name and a file name and download an Excel file of the results (EPPlus Excel library) and download them. To encapsulate the information needed to save the Excel File I created the following view model:
public class ExcelDownloadView
{
public List<Person> Persons { get; set; }
//ConvertToDataTable is a List extension method return converts the list and its properties
//into a table where columns are the properties
public DataTable DataTable { get { return Persons.ConvertToDataTable(); } }
public string SheetName { get; set; }
public string FileName { get; set; }
}
Here's what my view looks like, and on the initial GET request, everything comes out fine:
#model ExportToExcel.Models.ExcelDownloadView
#using ExportToExcel.Helpers
#{
ViewBag.Title = "Index";
}
<div id="grid">
#Html.ListToTable(Model.Persons)
</div>
<div>
This is the number of rows: #Model.DataTable.Rows.Count (correct)
</div>
#using (Html.BeginForm())
{
<input name="sheetName" type="text" /><br />
<input name="fileName" type="text" /><br />
<input type="submit" value="Click to download Excel file..." />
}
When I try to submit the form I get an error in my ConvertToDataTable method:
public static DataTable ConvertToDataTable<T>(this List<T> data)
{
DataTable dt = new DataTable();
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
dt.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
//*****error is at the start of the foreach loop
foreach (T item in data)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = props[i].GetValue(item);
}
dt.Rows.Add(values);
}
return dt;
}
Controller:
[HttpGet]
public ActionResult Index()
{
ExcelDownloadView model = new ExcelDownloadView();
model.Persons = Person.GetPersons().ToList();
var dataTable = model.Persons.ConvertToDataTable();
return View(model);
}
[HttpPost]
public ActionResult Index(ExcelDownloadView viewModel)
{
//DataTable property takes the List<Person> of the view model and turns it into
//a datable for use in the following Excel-file-creating function
/*--> if nonWorkingVersion is commented out, the program programs with a NullReferenceException*/
//DataTable nonWorkingVersion = viewModel.DataTable;
//if a re-seed the DataTable, everything works fine
DataTable dt = Person.GetPersons().ToList().ConvertToDataTable();
using (ExcelPackage pck = new ExcelPackage())
{
//Create the worksheet
ExcelWorksheet ws = pck.Workbook.Worksheets.Add(viewModel.SheetName);
//Load the datatable into the sheet, starting from cell A1. Print the column names on row 1
ws.Cells["A1"].LoadFromDataTable(dt, true);
//Format the header for column 1-3
using (ExcelRange rng = ws.Cells["A1:C1"])
{
rng.Style.Font.Bold = true;
rng.Style.Fill.PatternType = ExcelFillStyle.Solid; //Set Pattern for the background to Solid
rng.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(79, 129, 189)); //Set color to dark blue
rng.Style.Font.Color.SetColor(Color.White);
}
//Example how to Format Column 1 as numeric
using (ExcelRange col = ws.Cells[2, 1, 2 + dt.Rows.Count, 1])
{
col.Style.Numberformat.Format = "#,##0.00";
col.Style.HorizontalAlignment = ExcelHorizontalAlignment.Right;
}
//Write it back to the client
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", "attachment; filename=" + viewModel.FileName);
Response.BinaryWrite(pck.GetAsByteArray());
}
return View(viewModel);
}
In the GET version of Index method the data is there and correct. Why is the data not showing up correctly in my POST version? If I modify the view and remove references to the Persons property, a diaglog box will pop up and I can download the file I want. Why isn't the POST version receiving the data for Persons from the GET version?
First thing is to realize that these actions are called for two separate requests. First is called when user navigated to the page, and second is called when user saw the page, filled out the form and submitted it.
Next is the fact that in ASP.NET MVC you do not have state. The only info you have is the one posted form client. So even though there was some object created on the previous request, this is object has nothing to do with current request. In your form has two inputs - these are the pieces you get in request.
The correct way to handle this is what you have already implemented. That is if you need some data base querying during the request - generally you need to do it on every request.
Related
How can I pass multi-select values from ViewBag to Controller and save in respective data type for Db. With my current Code I am able to take along the values selected in View until the Action method, but post updating Contoller saves only the last values from the selected list for the Property.
It should store info in this format
LeaseID = 1,2,3
LeaseStatus will store as Rented, Vaccant, OwnerOccupied
Currently it saves like = 3 (If selected all 3)
Below is my Action Method
public ActionResult ReviewProperty(Property model, List<Leasing> LeasingStatus)
{
int id = model.PropId;
var uName = User.Identity.Name;
if (!ModelState.IsValid)
{
return View(model);
}
using (Db db = new Db())
{
Property dto = db.Properties.Find(id);
dto.OwnerName = model.OwnerName;
dto.OwnerMobNumber = model.OwnerMobNumber;
dto.AlterContactNum = model.AlterContactNum;
dto.OwnerEmail = model.OwnerEmail;
dto.PropertyStatus = model.PropertyStatus;
db.SaveChanges();
foreach(var lease in LeasingStatus)
{
dto.LeaseID = lease.LeaseID;
}
Leasing leaseDTO = db.Leasings.Where(x => x.LeaseID == dto.LeaseID).ToArray().ToList().FirstOrDefault();
dto.LeaseStatus = leaseDTO.LeaseStatus;
db.Properties.Add(dto);
db.SaveChanges();
}
TempData["SM"] = "Changes successfully updated!";
return RedirectToAction("MyPendingTask");
}
View
#for (int i = 0; i < ViewBag.ListLease.Count; i++)
{
<div class="form-row">
<div class="form-check">
<input type="checkbox" id="#ViewBag.ListLease[i].LeaseStatus" name="[#i].LeaseID" value="#ViewBag.ListLease[i].LeaseID" checked="#ViewBag.ListLease[i].isChecked" />
<label class="form-check-label">
#ViewBag.ListLease[i].LeaseStatus
</label>
</div>
</div>
}
You are saving the data outside the loop so that you save the data inside the loop, this will save all your data, all ids
foreach (var lease in LeasingStatus)
{
dto.LeaseID = lease.LeaseID;
Leasing leaseDTO = db.Leasings.Where(x => x.LeaseID == dto.LeaseID).ToArray().ToList().FirstOrDefault();
dto.LeaseStatus = leaseDTO.LeaseStatus;
db.Properties.Add(dto);
db.SaveChanges();
}
I'm trying to create a drop down list for from a dataset column in my SQL Server database. I have successfully linked the data. However, in view, the dropdown list data appears to have a vertically text.
Please see the screen captured below:
What causes this? Please help!
I'm just going to post the relevant code to easy to see.
Here is the line of the html code (I put index 0 for savedCompCoList for testing only to only get the first row):
<div>#Html.DropDownListFor(x => x.objBV.objCompCo.SavedCompCoSelected, new SelectList(Model.objBV.objCompCo.SavedCompCoList[0].CompCo_ID_With_date_List), "Select List", new { style = "width: 250px;" }))</div>
Using xmlDocument for connection to database:
public static XmlDocument GetSavedCompCo()
{
XmlDocument xmlTmp = DatabaseLib.RunStoredProcedure(UDV.spGetSavedCompCoListBV, UDV.connStringUserDB);
return xmlTmp;
}
Using Web method:
[WebMethod]
public XmlDocument GetSavedCompCo() { return BDOLibrary_Val_BV.CompsLib.GetSavedCompCo(); }
My model - here is the loop that loop though (this may be the cause):
public class CompCo
{
private readonly BDOWebService.BDOWebService webS = new BDOWebService.BDOWebService(); //EC: web service
//EC: variables
public List<SavedCompCo> SavedCompCoList { get; set; }
public int SavedCompCoSelected { get; set; }
public CompCo()
{
initSavedCompCoList();
Comps = new List<Company>();
}
private void initSavedCompCoList()
{
SavedCompCoList = new List<SavedCompCo>();
XmlDocument xmlTmp = webS.GetSavedCompCo();
XmlNodeList nodeListSavedCompCo_ID_With_Date = xmlTmp.GetElementsByTagName("CompCo_ID_With_Date");
for (int i = 0; i < nodeListSavedCompCo_ID_With_Date.Count; i++)
{
SavedCompCo SavedCompCoTemp = new SavedCompCo();
SavedCompCoTemp.CompCo_ID_With_date_List = nodeListSavedCompCo_ID_With_Date[i].InnerText.Trim();
SavedCompCoList.Add(SavedCompCoTemp);
}
}
}
Please help and thanks in advance!
I'm writing an ASP.NET web app (university task for exam). I have a database which has columns like Id, Name, Age, SumNote. First of all I had to make a partial view with top 5 students in database:
This method to get top 5 students
public class HomeController : Controller
{
StudentContext db = new StudentContext();
public ActionResult ShowTopFive ()
{
var allStudents = db.Students.OrderByDescending(s => s.SumNote).Take(5);
return PartialView(allStudents);
}
}
This is the patrial View:
#model IEnumerable<Univercity.Models.Student>
<div id="results">
<h4>Best 5 students</h4>
<ul>
#foreach (var item in Model)
{
<li>#item.Name, Summ of notes: #item.SumNote</li>
}
</ul>
</div>
and with this one I got the list of students in my webpage
<div>
<h5>Show top 5 students</h5>
</div>
<div>
#using (Ajax.BeginForm("ShowTopFive", new AjaxOptions { UpdateTargetId = "results" }))
{
<input type="submit" value="Show"/>
}
<div id="results"></div>
</div>
the output result looks like this:
Ivanov Mikhail, Summ of notes: 16
Kozlov Pete, Summ of notes: 12
Mary Ann, Summ of notes: 11
I also need to save it as text file. Can't figure out how? May be there is a way to change something in Ajax code?
Thanks in advance. Hope someone know how to do it. Google didn't help
You could create a controller action method which uses FileStreamResult by iterating the list created from ToList() and write necessary property values into a stream, then use Controller.File() overload which accepts stream to let user download text file:
public ActionResult GetTextFile()
{
var topFiveStudents = db.Students.OrderByDescending(s => s.SumNote).Take(5).ToList();
if (topFiveStudents != null && topFiveStudents.Count > 0)
{
string fileName = "something.txt";
// create a stream
var ms = new MemoryStream();
var sw = new StreamWriter(ms);
foreach (var students in topFiveStudents)
{
// iterate the list and write to stream
sw.WriteLine(string.Format("{0}, Sum of notes: {1}", students.Name, students.SumNote));
}
sw.Flush();
ms.Position = 0;
// return text file from stream
return File(ms, "text/plain", fileName);
}
else
{
// do something else
}
}
Afterwards, create an anchor link pointed to that action method mentioned above inside partial view:
#Html.ActionLink("Export to TXT", "GetTextFile", "ControllerName")
I have two tables tblProduct & tblImages. tblImages will have multiple images for a product and has a foreign key related to tblProduct. I have a problem with inserting data into multiple tables.
This is my view code:
<!-- Text Boxes and dropdown lists for tblProduct above and below is for adding files to tblImages-->
<div class="col-md-10">
<input multiple type="file" id="file" name="file" />
</div>
and here is my controller's code:
public ActionResult AddProduct_Post([Bind(Include = "productName, productDescription, productPrice, productCategory")]tblProduct tblProduct,List<HttpPostedFileBase> file)
{
List<tblImage> prodImages = new List<tblImage>();
var path = "";
foreach (var item in file)
{
if (item != null)
{
tblImage img = new tblImage();
img.ImageFile = new byte[item.ContentLength];
img.ImageName = string.Format(#"{0}.JPG", Guid.NewGuid());
img.vend_ID = Convert.ToInt32(Session["userID"]);
item.InputStream.Read(img.ImageFile, 0, item.ContentLength);
path = Path.Combine(Server.MapPath("~/Content/img"), img.ImageName);
item.SaveAs(path);
prodImages.Add(img);
}
}
tblProduct.venodrID = Convert.ToInt32(Session["userID"]);
tblProduct.tblImages = prodImages;
if (ModelState.IsValid)
{
db.tblProducts.Add(tblProduct);
db.SaveChanges();
int latestProdID = tblProduct.productID;
foreach (tblImage tblImg in tblProduct.tblImages)
{
tblImg.prod_ID = latestProdID;
db.Entry(tblImg).State = EntityState.Modified;
}
db.SaveChanges();
return RedirectToAction("DashBoard");
}
ViewBag.productCategory = new SelectList(db.tblCategories, "categoryID", "categoryName");
ViewBag.vendorGender = new SelectList(db.tblGenders, "genderId", "genderName");
return View();
}
I put a breakpoint on ModelState.IsValid and it is not going inside the if conditions and is returning back the same view without posting the data. Kindly tell me how to solve this issue
P.S: I am new to ASP.NET MVC
[Bind(Include =
you can the check parameter you bind in action method is similar what you defined in the View else you can remove the bind attribute and try this will help you to find what actually error is..
I am building a web app in C# and ASP.Net with an MVC framework. I have the app running on my local desktop (for now). The app has a SQL backend that stores all my data. I am able to pull the data from the SQL db successfully through a number of stored procedures. The data is able to successfully be transferred from the stored procedures all the way up to my view from the controller.
Part of the data being transferred to the view is a byte[] from an image stored in the db (datatype is VARBINARY(MAX)). In short, I am trying to get the data from this byte[] to display as a background image in a div. This div acts as a single image in a Bootstrap carousel.
Initially, I had the following as my controller:
public ActionResult Dashboard()
{
DashboardViewModelHolder holder = new DashboardViewModelHolder();
DiscoveryService discoveryService = new DiscoveryService();
holder.national_Elected_Officials = new List<National_Elected_Officials_Model>();
National_Elected_Officials_Model n = new National_Elected_Officials_Model();
foreach (List<object> official in discoveryService.retrieve_National_Elected_Officials())
{
for(int i = 0; i <= official.Count; i++)
{
int id = int.Parse(official.ElementAt(0).ToString());
string fname = official.ElementAt(1).ToString();
string lname = official.ElementAt(2).ToString();
byte[] pictureByteArray = (byte[])official.ElementAt(3);
string position = official.ElementAt(4).ToString();
string party = official.ElementAt(5).ToString();
string bio = official.ElementAt(6).ToString();
int yearsOfService = int.Parse(official.ElementAt(7).ToString());
int terms = int.Parse(official.ElementAt(8).ToString());
string branch = official.ElementAt(9).ToString();
Image picture = image_Adapter.byteArrayToImage(pictureByteArray);
n.ElectedOfficialID = id;
n.FirstName = fname;
n.LastName = lname;
n.Picture = picture;
n.Position = position;
n.Party = party;
n.Bio = bio;
n.YearsOfService = yearsOfService;
n.Terms = terms;
n.Branch = branch;
}
holder.national_Elected_Officials.Add(n);
}
return View(holder);
}
My thought process was that I would just call n.Picture in my view and it would render the picture. After several tries and tutorials later, I left n.Picture as a byte[] and processed it in its own ActionResult method as seen below:
public FileContentResult Image(byte[] pictureByteArray)
{
return new FileContentResult(pictureByteArray, "image/jpeg");
}
I call this in my view as the following:
<div class="fill" style="background-image:src(#Url.Action("Image", electedOfficial.Picture))"></div>
electedOfficial is a reference to the model being set in the controller (n.Picture).
Is there something that I am missing?
EDIT 1
I forgot to add that the div returns null when I debug and step through the code. This is because the line with the div never gets called on when debugging. If I have it set as Url.Action, the program will actually go to the controller before hitting the line. If I do Html.Action, the program will skip the line and go to the controller after. Both will return null as a result which returns an error on the controller side since nulls arent allowed.
Edit 2
I tried changing the div tag to the following:
<div class="fill" style="background-image:src(#{Html.Action("Image", electedOfficial.Picture);})"></div>
By putting the {} in the debugger actually parses the line as I step through. Now, the problem is that the controller is not receiving the value being passed to it from electedOfficial.Picture. Just to confirm, this variable does hold the correct value in the view.
If you have the full byte[] in your model, then you can put the data directly into the view:
<div style="background:url( data:image/jpeg;base64,#Convert.ToBase64String(electedOfficial.Picture) )"></div>
This will work without the need for a separate controller that returns a FileContentResult, but will be a longer initial page load since the user will download all of the images along with the page HTML.
If you want to use a Controller endpoint so the images can be referenced as a URL in the src attribute and downloaded after the HTML has rendered then you are not too far off. It would work better to have the controller accept ElectedOfficialID and return the FileContentResult from that.
public FileContentResult Image(int electedOfficialId)
{
byte[] picture = GetPicture(electedOfficialId);
return new FileContentResult(picture, "image/jpeg");
}
Simples way of doing that would be encoding image as base64 string and add new string property eg PictureAsString to model instead having Picture
controller
n.PictureAsString = Convert.ToBase64String(pictureByteArray)
view
<div style="background:url(data:image/jpeg;base64,#electedOfficial.PictureAsString )" ></div>
use handler(ASHX). and call handler url in src.
public class MyHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
//Get Id from somewhere
//Get binary data
context.Response.ContentType = "application/octet-stream";
context.Response.BinaryWrite(bytes);
}
}
You can convert your byte array into a picture using this way:
Convert your byte array into a base64 string.
Display it in <img> tag.
Here is the code:
#{
var base64 = Convert.ToBase64String(Model.ByteArray);
var imgSrc = String.Format("data:image/gif;base64,{0}", base64);
}
<img src="#imgSrc" />