ASP.NET MVC multiple parameters and models - c#

Description: I have a model WorkOrder which contains WorkOrderForm model (start_date, end_date, organization), a list of models (the actual orders) and page parameters (page, page_size, page_count). The main idea is to display work orders, and because there is a large amount of them I have to filter the data; work orders get pulled from a database server (not local). On the initial View i prompt for start_date and end_date, I use DataType.Date and for organization i use string, this information get's stored in the model which I then pass in to the HttpPost. It extracts the data and displays it. Now, because there is A LOT of orders, I made costume pages to sort data, and I use 2 variables, page and page_size, which are displayed and can be set on the view after a valid WorkOrderForm was submitted.
Problem: The problem I am facing right now is that I can't seem to pass the 2 parameters page and page_size, from the view back to the controller at the same time. Both of them work but seem to reset each other, Example: I am on page 4, set page_size from 20 to 50, and it resets page to 1, this one is alright, but the main on is when I chose a page, it will reset the page_size to default (20). All the submitting has to happen inside Html.BeginForm() otherwise i lose the information stored in my model.
EDIT: now using PagedList.
New Problem: when I select a page it calls the [httpget], resetting the model and page size. I tried to implement it all in the Index, but failed miserably.
WorkOrder:
public class WorkOrder
{
[Key]
public int Id { get; set; }
public IPagedList<mymodel> view_list { get; set; }
public WorkOrderForm work_form { get; set; }
}
Controller:
[HttpGet]
public ActionResult Index(WorkOrder model)
{
var list = new List<mymodel>();
model.view_list = list.ToPagedList(1,1);
return View(model);
}
[HttpPost]
public ActionResult Index(WorkOrder model, int? page, int? page_size, string start_date, string end_date, string org)
{
var list = new List<mymodel>();
if (ModelState.IsValid)
{
using (OracleDbctx ctx = new OracleDbctx())
{
//database stuff
int pageNumber = (page ?? 1);
int pageSize = (page_size ?? 20);
model.view_list = list.ToPagedList(pageNumber, pageSize);
return View(model);
}
}
ModelState.AddModelError("", "Incorrect Information submitted");
return View();
}
Page Info submission in my view:
#:Page #(Model.view_list.PageCount < Model.view_list.PageNumber ? 0 : Model.view_list.PageNumber) of #Model.view_list.PageCount
#Html.PagedListPager(Model.view_list, page => Url.Action("Index", "WorkOrder",
new { page, ViewBag.page_size, start_date = Model.work_form.start_date, end_date = Model.work_form.end_date, org = Model.work_form.org }));
Question: How do I go about passing both of the parameters from the view and at the same time keep my model information, even if i am only choosing to update 1 parameter? If you have suggestions on possible work around's I would be happy to hear some ideas, even pointing me in a direction would be appreciated. If there might be something I am missing please do say.
New Question: How to ether get PagedList to call the [httppost] Index, or whats the best way to implement something like this in the default Index controller?

Following implementation of the Index in the Controller worked
public ActionResult Index(WorkOrder model, int? page, int? page_size, string start_date, string end_date, string org)
{
if (model.work_form == null)
{
if (start_date != null && end_date != null && org != null)
{
var form = new WorkOrderForm
{
start_date = start_date,
end_date = end_date,
org = org
};
model.work_form = form;
}
}
if (model.work_form != null)
{
using (OracleDbctx ctx = new OracleDbctx())
{
//do database stuff
//var list = database(query).ToList()
int pageNumber = (page ?? 1);
int pageSize = (page_size ?? 20);
int count = list.Count;
if (pageNumber - 1 > count / page_size)
{
pageNumber = 1;
}
model.view_list = list.ToPagedList(pageNumber, pageSize);
return View(model);
}
}
return View();
}

Related

How to create a FileUpload view / viewmodel that isn't an entity in the database

I am working on an auction application and I am creating a method so that the admins can submit an excel spreadsheet that will create a new auction and store it in the database. So first I made a class (model) Uploadfile like this:
[NotMapped]
public class UploadFile
{
[Required]
public HttpPostedFileBase ExcelFile { get; set; }
}
I used NotMapped because I am trying to understand how to create and use models that aren't stored in my database and this is where my issue and misunderstanding lies.
I created a controller, which I did manually since UploadFile is not an entity with a key as such:
public class FileUploadsController : Controller
{
private AuctionEntities db = new AuctionEntities();
// GET: FileUploads
public ActionResult Index()
{
UploadFile UploadFile = new UploadFile();
return View(UploadFile);
}
[HttpPost]
public ActionResult Index(UploadFile UploadFile)
{
if (ModelState.IsValid)
{
if (UploadFile.ExcelFile.ContentLength > 0)
{
if (UploadFile.ExcelFile.FileName.EndsWith(".xlsx") || UploadFile.ExcelFile.FileName.EndsWith(".xls"))
{
XLWorkbook wb;
// in case if the file is corrupt
try
{
wb = new XLWorkbook(UploadFile.ExcelFile.InputStream);
}
catch (Exception ex)
{
ModelState.AddModelError(String.Empty, $"Check your file. {ex.Message}");
return View();
}
IXLWorksheet ws = null;
try // in case the sheet you are looking for is not found
{
ws = wb.Worksheet("sheet1");
}
catch
{
ModelState.AddModelError(String.Empty, "Sheet not found");
return View();
}
var firstRowUsed = ws.FirstRowUsed();
var auctionRow = firstRowUsed.RowUsed().RowBelow();
// create auction
string auctionName = auctionRow.Cell(1).Value.ToString();
DateTimeOffset startDate = DateTimeOffset.Parse(auctionRow.Cell(2).Value.ToString());
DateTimeOffset endDate = DateTimeOffset.Parse(auctionRow.Cell(3).Value.ToString());
string folderName = auctionRow.Cell(4).Value.ToString();
Models.Auction auction = new Models.Auction(auctionName, startDate, endDate, folderName);
db.Auctions.Add(auction);
// find the next table
var nextRow = auctionRow.RowBelow();
while (nextRow.IsEmpty())
{
nextRow = nextRow.RowBelow();
}
const int catNameCol = 1;
var catRow = nextRow.RowUsed().RowBelow();
// get categories from ws table and add to the auction
while (!catRow.Cell(catNameCol).IsEmpty())
{
string catName = catRow.Cell(1).Value.ToString();
int seqNo = Convert.ToInt32(catRow.Cell(2).Value.ToString());
string fileName = catRow.Cell(3).Value.ToString();
Cat cat = new Cat(auction.AuctionId, catName, seqNo, fileName);
auction.Cats.Add(cat);
catRow = catRow.RowBelow();
}
var findNextRow = catRow.RowBelow();
while (findNextRow.IsEmpty())
{
findNextRow = findNextRow.RowBelow();
}
const int itemNameCol = 1;
var itemRow = findNextRow.RowUsed().RowBelow();
while(!itemRow.Cell(itemNameCol).IsEmpty())
{
string itemName = itemRow.Cell(1).Value.ToString();
string itemDesc = itemRow.Cell(2).Value.ToString();
string catName = itemRow.Cell(3).Value.ToString();
string modelNo = itemRow.Cell(4).Value.ToString();
decimal retailValue = Convert.ToDecimal(itemRow.Cell(5).Value.ToString());
string fileName = itemRow.Cell(6).Value.ToString();
decimal initialBid = Convert.ToDecimal(itemRow.Cell(7).Value.ToString());
decimal increment = Convert.ToDecimal(itemRow.Cell(8).Value.ToString());
Cat itemCat = null;
foreach(var cat in auction.Cats)
{
if(catName == cat.CatName)
{
itemCat = cat;
}
}
Item item = new Item(itemName, itemDesc, modelNo, retailValue, fileName, startDate, endDate, initialBid, increment, null, null, null, itemCat);
itemCat.Items.Add(item);
itemRow = itemRow.RowBelow();
}
}
else
{
ModelState.AddModelError(String.Empty, "Only .xlsx and .xls files are allowed");
return View();
}
}
else
{
ModelState.AddModelError(String.Empty, "Not a valid file");
return View();
}
}
db.SaveChanges();
return View();
}
Next I thought I would try to create a view again so that I can display where the user uploads the file and see if my method works and this is where I have run into my lack of knowledge in asp.net.
So I tried to create a ViewModel as I have seen since the model I created before was a data model, so that I could use this viewmodel to display the upload on my view page. My ViewModel is simple and is:
public class FileUploadViewModel
{
public HttpPostedFileBase ExcelFile { get; set; }
}
Now, I wanted to create a view page for this viewmodel and it is still treating this model has an entity and giving me an error that it does not have a key etc. I need a viewpage that can access a model with the Excel file in it and I can't seem to figure out how to accomplish this. I have read up on viewmodels and I know how crucial they are in MVC, however I just can't seem to grasp on how to use them. Can someone please help me understand how to use one here?
Basically, I want to use this view page with my model or viewmodel:
My educated guess is that you are getting stuck in the "Add View" window.
You are probably selecting a template that requires a model (e.g. Create), selecting your FileUploadViewModel class as the model and also your context.
What this does is it causes the Visual Studio "wizard" to try to map the model internally to your context, which results on the error you see.
Instead, select Empty (without model) as the template which will gray out the Model and Data Context fields. Your view will then be created without errors.
You can then tell the view to expect your model by adding this at the top:
#model FileUploadViewModel
Make sure you fully qualify FileUploadViewModel (e.g. include the namespace in front).
Your methods should now use the model you specified at the top of the view:
public ActionResult Index()
{
FileUploadViewModel UploadFile = new FileUploadViewModel();
return View(UploadFile);
}
[HttpPost]
public ActionResult Index(FileUploadViewModel UploadFile)
{
}
You do not need the [NotMapped] attribute anywhere here.

How to get the username of a logged in user and compare it to get the id

I want to be able to get the username of the logged in user in my C# ASP.net application so I can get the ID of that user. I then want to perform an edit so they can apply to be part of an appointment.
With my current code, I click the link and I just get a HTTP 400 error. I have debugged this and the ID is coming through as the correct value. Is there any reason that its not being attached to the url?
// GET:
public ActionResult VolunteerCeremony(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
string userName = string.Empty;
//if (System.Web.HttpContext.Current != null &&
// System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
//{
// System.Web.Security.MembershipUser usr = System.Web.Security.Membership.GetUser();
// if (usr != null)
// {
// userName = usr.UserName;
// }
//}
var getVolunteerId = (from u in db.Volunteers
where WebSecurity.CurrentUserName == u.Username
select u.VolunteerId).FirstOrDefault();
Volunteer volunteer = db.Volunteers
.Include(p => p.Appointments)
.Where(i => getVolunteerId == id)
.FirstOrDefault();
if (volunteer == null)
{
return HttpNotFound();
}
PopulateAssignedCeremonyData(volunteer);
return View(volunteer);
}
// POST: /Player/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult VolunteerCeremony(int? id, string[] selectedOptions)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var getVolunteerId = (from u in db.Volunteers
where WebSecurity.CurrentUserName == u.Username
select u.VolunteerId).FirstOrDefault();
var updateVolunteerWithCeremony = db.Volunteers
.Include(p => p.Appointments)
.Where(i => getVolunteerId == id)
.Single();
try
{
UpdateVolunteerCeremonies(selectedOptions, updateVolunteerWithCeremony);
db.Entry(updateVolunteerWithCeremony).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateAssignedCeremonyData(updateVolunteerWithCeremony);
return View(updateVolunteerWithCeremony);
}
EDIT This is where I'm calling the method
else if(Request.IsAuthenticated && HttpContext.Current.User.IsInRole("Volunteer"))
{
<li>#Html.ActionLink("Appointments", "Create", "Appointments")</li>
<li>#Html.ActionLink("Join Ceremony", "VolunteerCeremony", "Volunteers")</li>
}
EDIT 2 Here's my ViewModel that I used for the many to many relationship I have:
public class VolunteerCeremonyVM
{
public int AppointmentId { get; set; }
public string DetailsOfAppointment { get; set; }
public bool Assigned { get; set; }
public VolunteerCeremonyVM()
{
this.AppointmentId = AppointmentId;
this.DetailsOfAppointment = DetailsOfAppointment;
this.Assigned = Assigned;
}
}
Based on further clarification, you will want to update your ViewModel to include a VolunteerId. So it should look something like:
public class VolunteerCeremonyVM
{
public int AppointmentId { get; set; }
public string DetailsOfAppointment { get; set; }
public bool Assigned { get; set; }
public int VolunteerId { get; set; }
}
Notice that I removed the default constructor as it wasn't doing anything. When you construct the object you can use code like the following:
var model = new VolunteerCeremonyVM {
AppointmentId = 10,
VolunteerId = 50,
//etc
};
As this will set all of the properties after the basic object is constructed.
Then, you will want to pass this view model into the view that is generating your action links like so: return View(model); in whatever controller action you're using.
Inside the view, you will make use of this new information. Specifically, the Html.ActionLink method requires that any extra parameters are passed to it in a route dictionary. It uses this route dictionary to build the URL that is generated for the anchor tag. You can call Html.ActionLink with an anonymous object for its fourth parameter to specify the route dictionary. The id will be the name of the parameter and Model.VolunteerId will be the value of the current volunteer.
Change the Html.ActionLink call to the following to pass an id:
#Html.ActionLink("Join Ceremony",
"VolunteerCeremony",
"Volunteers",
new { id = Model.VolunteerId },
null)
This should fill in the id parameter of the ActionLink with the currently logged in user's userId which should generate the following link: /Volunteers/VolunteerCeremony/3 or whatever the ID actually is.
For the form side, you will also need a hidden field on the form so the correct id is sent along with the request. You will need to add a VolunteerId to the form ViewModel that you create when generating the form page. Once you've done that, inside the form itself, you will need to add the following:
Html.Hidden("id", Model.VolunteerId)
This creates an <input type="hidden" name="id" value="3" /> tag that will be sent with the form on post back to the server. Since the tag's name is id this will match with the id that's defined in your controller action.

ASP EF Increment / Decrement Value with + / - buttons

So I'm new to ASP and EF and I am wondering how to do this incredibly basic operation, as well as a few questions to go along with doing it.
Currently I have a table we will call Resource;
class Resource
{
int current;
int min;
int max;
};
Right now I have the default CRUD options for this. What I would like is a + / - button on the main list that will manipulate the current value of each resource and update the value in the DB and on screen.
There are also certain operations I'd like to run such as "AddFive" to a selected group of resources.
So my questions;
How do I do this?
Is this scalable? If someone is constantly hitting the buttons this is obviously going to send a lot of requests to my DB. Is there any way to limit the impact of this?
What are my alternatives?
Thanks.
Edit:
To clarify the question; here are my post functions. How / where do I add these in my view to get a button showing for each resource. I just want the action to fire and refresh the value rather than redirect to a new page.
#Html.ActionLink("+", "Increment", new { id = item.ID })
public void Increment(int? id)
{
if (id != null)
{
Movie movie = db.Movies.Find(id);
if (movie != null)
{
Increment(movie);
}
}
}
[HttpPost, ActionName("Increment")]
[ValidateAntiForgeryToken]
public ActionResult Increment([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Resource resource)
{
if ((resource.Current + 1) < (resource.Max))
resource.Current++;
return View(resource);
}
It sounds like the main issue you are having is creating a list of movies on the front end and updating the details for a specific one.
The key here is that you will need to either wrap a form around each item and have that posting to your update controller or use ajax / jquery to call the controller instead.
I have given you an example of the first one. Once the update controller is hit it will redirect to the listing page which will then present the updated list of movies.
Below is a minimal working example of how to wire this up. I've not included any data access code for brevity but have included psuedo code in the comments to show you where to place it.
Please let me know if you have any futher questions.
Controller
public class MoviesController : Controller
{
public ViewResult Index()
{
// Data access and mapping of domain to vm entities here.
var movieListModel = new MovieListModel();
return View(movieListModel);
}
public ActionResult Increment(IncrementMovieCountModel model)
{
// Put breakpoint here and you can check the value are correct
var incrementValue = model.IncrementValue;
var movieId = model.MovieId;
// Update movie using entity framework here
// var movie = db.Movies.Find(id);
// movie.Number = movie.Number + model.IncrementValue;
// db.Movies.Save(movie);
// Now we have updated the movie we can go back to the index to list them out with incremented number
return RedirectToAction("Index");
}
}
View
#model WebApplication1.Models.MovieListModel
#{
ViewBag.Title = "Index";
}
<h2>Some Movies</h2>
<table>
<tr>
<td>Id</td>
<td>Name</td>
<td>Increment Value</td>
<td></td>
</tr>
#foreach (var movie in Model.MovieList)
{
using (Html.BeginForm("Increment", "Movies", FormMethod.Post))
{
<tr>
<td>#movie.Id #Html.Hidden("MovieId", movie.Id)</td>
<td>#movie.Name</td>
<td>#Html.TextBox("IncrementValue", movie.IncrementValue)</td>
<td><input type="submit" name="Update Movie"/></td>
</tr>
}
}
</table>
Models
public class MovieListModel
{
public MovieListModel()
{
MovieList = new List<MovieModel> {new MovieModel{Id=1,Name = "Apocalypse Now",IncrementValue = 3}, new MovieModel {Id = 2,Name = "Three Lions", IncrementValue = 7} };
}
public List<MovieModel> MovieList { get; set; }
}
public class MovieModel
{
public int Id { get; set; }
public string Name { get; set; }
public int IncrementValue { get; set; }
}
public class IncrementMovieCountModel
{
public int IncrementValue { get; set; }
public int MovieId { get; set; }
}

Lose MongoDB ObjectId value when passing through Actions

In my MVC Controller, I have this code (after adding an object, I redirect user to edit that object):
[PrivilegeRequirement("DB_ADMIN")]
public ActionResult Add()
{
MongoDataContext dc = new MongoDataContext();
var model = new ModelObj();
dc.Models.Insert(model);
return this.RedirectToAction("Edit", new { pId = model.Id, });
}
[PrivilegeRequirement("DB_ADMIN")]
public ActionResult Edit(ObjectId? pId)
{
MongoDataContext dc = new MongoDataContext();
var model = dc.Models.FindOneById(pId);
if (model == null)
{
Session["error"] = "No model with this ID found.";
return this.RedirectToAction("");
}
return this.View(model);
}
However, pId is always null, making the FindOneById always return null. I have debugged and made sure that the Id had value when passing from Add Action. Moreover, I tried adding a testing parameter:
[PrivilegeRequirement("DB_ADMIN")]
public ActionResult Add()
{
MongoDataContext dc = new MongoDataContext();
var model = new ModelObj();
dc.Models.Insert(model);
return this.RedirectToAction("Edit", new { pId = model.Id, test = 10 });
}
[PrivilegeRequirement("DB_ADMIN")]
public ActionResult Edit(ObjectId? pId, int? test)
{
MongoDataContext dc = new MongoDataContext();
var model = dc.Models.FindOneById(pId);
if (model == null)
{
Session["error"] = "No model with this ID found.";
return this.RedirectToAction("");
}
return this.View(model);
}
When I debug, I received the test parameter in Edit Action with value of 10 correctly, but pId is null. Please tell me what I did wrong, and how to solve this problem?
I would suspect that the ObjectId is not serializing/deserializing correctly. Given that it doesn't make for a great WebAPI anyway, I generally use a string and convert within the method to an ObjectId via the Parse method (or use TryParse):
public ActionResult Edit(string id, int? test)
{
// might need some error handling here .... :)
var oId = ObjectId.Parse(id);
}
You can use ToString on the ObjectId to convert it to a string for calling:
var pId = model.Id.ToString();

ASP.NET MVC TryValidateModel() Issues when Model is Modified

I have a two step form process where the first set of data is stored in session.
[IsMp4File]
[Required(ErrorMessage = "* Please select a video to upload")]
public HttpPostedFileBase VideoClip { get; set; }
[Required(ErrorMessage = "* Please select a thumbmail image")]
public HttpPostedFileBase VideoThumbnail{ get; set; }
public string VideoFileName { get { return VideoClip.FileName; } }
public NewsWizardStep CurrentStep { get; set; }
...
public enum NewsWizardStep : int
{
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6
}
Controller
public ActionResult TvCreate(TvNewsVideoVM modelVM)
{
if (modelVM.CurrentStep == NewsWizardStep.Two)
{
var sessionModel = ((TvNewsVideoVM)Session["TvModelVM"]);
modelVM.VideoClip = sessionModel.VideoClip;
modelVM.VideoThumbnail = sessionModel.VideoThumbnail;
}
if (TryValidateModel(modelVM))
{
...
}
}
TryValidateModel(modelVM) returns false, saying VideoClip and VideoThumnail are required, despite mapping them from the seesionModel to the viewModel. I have added a breakpoint and checked they are not null.
It looks like there is some underlying functionality I am not aware of regarding how ModelState and ValidateModel() work , I just don't know what.
UPDATE
I wouldn't say I have resolved the issue but figured out a workaround that isn't that pretty, By going into the ModelState it is possible to set the ModelValue using SetModelValue() then manually remove the error from the model state and then call TryValidateModel() - you might not even have to add the values just remove the error I have not tried. Here is my work around.
if (modelVM.CurrentStep == NewsWizardStep.Two)
{
var sessionModel = ((MtTvNewsVideoVM)Session["MtTvModelVM"]);
modelVM.VideoClip = sessionModel.VideoClip;
modelVM.VideoThumbnail = sessionModel.VideoThumbnail;
ModelState.SetModelValue("VideoClip", new ValueProviderResult(sessionModel.VideoThumbnail, sessionModel.VideoFileName, CultureInfo.CurrentCulture));
ModelState.SetModelValue("VideoThumbnail", new ValueProviderResult(sessionModel.VideoClip, sessionModel.VideoFileName, CultureInfo.CurrentCulture));
ModelState["VideoClip"].Errors.RemoveAt(0);
ModelState["VideoThumbnail"].Errors.RemoveAt(0);
}
During the model binding the DefaultModelBinder validates your action parameters.
So when the execution hits your public ActionResult TvCreate(TvNewsVideoVM modelVM) method
the ModelState is already containing the validation errors.
When you call TryValidateModel it doesn't clear the ModelState so the validation errors remain there that is why it returns false. So you need to clear the ModelState collection if you want to redo the validation later manually:
public ActionResult TvCreate(TvNewsVideoVM modelVM)
{
ModelState.Clear();
if (modelVM.CurrentStep == NewsWizardStep.Two)
{
var sessionModel = ((TvNewsVideoVM)Session["TvModelVM"]);
modelVM.VideoClip = sessionModel.VideoClip;
modelVM.VideoThumbnail = sessionModel.VideoThumbnail;
}
if (TryValidateModel(modelVM))
{
...
}
}

Categories