Combining multiple (almost) identical methods with different views? - c#

In my current MVC Project, I have a controller with the following method:
public ActionResult Index(string answer)
{
using (S3WEntities1 ent = new S3WEntities1())
{
afqList.Question = ent.Questions.Where(w => w.QuQuestionId == 1).Select(s => s.QuQuestion).FirstOrDefault().ToString();
afqList.Answers = ent.Answers.Where(w => w.AnsQuestionId == 1).Select(s => s.AnsAnswer).ToList();
}
return View(afqList);
}
However, this method is repeated 5 times, all with the only difference being that the number in (w => w.QuQuestionId == x) and (w => w.AnsQuestionId == x) changes, as well as having 5 different, although still similar, views for each method. How would I make this code a lot better than having 5 almost identical methods, but all still having different views? Thanks in advance!
EDIT:
I should also mention that in each of these methods, the corresponding view has a
#using (Html.BeginForm("Question3", "ControllerName", "FormMethod.Post))
and so need to call different methods based on which one comes next and is stated in the views.

Replace the number with x which will be passed as parameter
public ActionResult Index(string answer, int x)
{
using (S3WEntities1 ent = new S3WEntities1())
{
afqList.Question = ent.Questions.Where(w => w.QuQuestionId == x).Select(s => s.QuQuestion).FirstOrDefault().ToString();
afqList.Answers = ent.Answers.Where(w => w.AnsQuestionId == x).Select(s => s.AnsAnswer).ToList();
}
return View(afqList);
}

First add to your model:
public string NextQuestion { get; set; }
Then you can use it in your action and view:
public ActionResult Index(string answer, int questionId)
{
using (S3WEntities1 ent = new S3WEntities1())
{
afqList.Question = ent.Questions.Where(w => w.QuQuestionId == questionId).Select(s => s.QuQuestion).FirstOrDefault().ToString();
afqList.Answers = ent.Answers.Where(w => w.AnsQuestionId == questionId).Select(s => s.AnsAnswer).ToList();
}
afqList.NextQuestion = string.Format("Question{0}", questionId + 1);
return View(afqList);
}
Now in the View:
#using (Html.BeginForm(afqList.NextQuestion, "ControllerName", "FormMethod.Post))

Related

Sentence Where in [...] ASP.net c# mvc Qlink

I know how to do a where in Qlink... p.e.
public ActionResult Index(){
using (DBEntities db = new DBEntities())
{
return View(db.vw_values.Where(m => m.value == 1).ToList());
}
}
but I want to do a Where in but I´ve not found how to implement it... p.e.
... int[] values = {1,2,3, ...}
return View(db.vw_values.Where(m => m.value == values).ToList()); ...
is it posible? (The idea, I know that this code is wrong)
All you have to do is:
public ActionResult Index()
{
using (DBEntities db = new DBEntities())
{
return View(db.vw_values.Where(m => values.Contains(m.value).ToList());
}
}
Here
you can learn more about LINQ in C# and all the other cool things you can do with it.

Populating a View Model with a foreach loop when one field could be null?

I have some old tables from an old database which I am trying to use to populate a View Model. The controller action looks for records in one old table where the email matches the current users email and then populates the View Model based on a list derived from those matches. This is my controller action:
public PartialViewResult OldCourses()
{
var email = User.Identity.GetEmail();
var reglist = nb.old_register
.Where(i => i.email == email).ToList();
if (reglist != null)
{
var viewModel = new OldCourseViewModel();
foreach (var reg in reglist)
{
viewModel.StudentID = reg.studentid;
viewModel.CourseTitle = nb.old_courses.FirstOrDefault(j => j.courseid == nb.old_Cohorts.FirstOrDefault(h => h.cohortname == reg.cohort).modulename).coursetitle;
viewModel.Cohort = reg.cohort;
viewModel.Progpct = nb.student_progress.FirstOrDefault(k => k.studentid == reg.studentid).percentage;
};
return PartialView("OldCourses", viewModel);
}
It is possible for viewModel.Progpct to return null as there may not be an entry in the student_progress table for each record in reglist. This means I get an error, Object reference not set to an instance of an object.
The View Model is as follows:
public class OldCourseViewModel
{
public int StudentID { get; set; }
public string CourseTitle { get; set; }
public string Cohort { get; set; }
public decimal? Progpct { get; set; }
public string icon_class { get; set; }
public string icon_colour_class { get; set; }
public string titleabbrev { get; set; }
}
The field 'percentage' from the old table is as follows:
public Nullable<decimal> percentage { get; set; }
None of the old tables have foreign key relationships and I can't change any of those tables in any way.
I appreciate that the foreach loop can't handle the null, even though the field is nullable in the View Model.
My first question is what is the best way to handle this situation so that this field can be returned null when it is null?
My second question is, if there are multiple records in the reglist, how can I make the foreach loop cycle through each record in the reglist and generate a new record in the View Model for each of those in the reglist?
Edit - On Stephen's suggestion, I did the following:
var pctcheck = nb.student_progress.FirstOrDefault(k => k.studentid == reg.studentid);
if (pctcheck != null)
{
viewModel.Progpct = pctcheck.percentage;
}
This answers my first question correctly. Stephen also answered my second question correctly in the comments.
The accepted answer is the best way of completing my first question, rather than the above I used:
viewModel.Progpct = nb.student_progress.FirstOrDefault(k => k.studentid == reg.studentid)?.percentage;
Which uses the ?. operator to do the same job as the if null check.
For the first question, even if you fixed it, you may use, in new versions of c#, the ?. operator
viewModel.Progpct = nb.student_progress.FirstOrDefault(k => k.studentid == reg.studentid)?.percentage;
in older versions, you may use
viewModel.Progpct = nb.student_progress.Where(k => k.studentid == reg.studentid).Select(m => (decimal?)m.percentage).FirstOrDefault();
for the second question, you could simplify with something like that.
Your model will be an IEnumerable<OldCourseViewModel>
var email = User.Identity.GetEmail();
var model = nb.old_register.Where(i => i.email == email)
.Select(reg => new OldCourseViewModel {
StudentID = reg.studentid,
CourseTitle = nb.old_courses.FirstOrDefault(j => j.courseid == nb.old_Cohorts.FirstOrDefault(h => h.cohortname == reg.cohort)?.modulename)?.coursetitle;
Cohort = reg.cohort,
Progpct = nb.student_progress.FirstOrDefault(k => k.studentid == reg.studentid)?.percentage
});
return PartialView("OldCourses", model);

How to use if else in this action?

How do you write an if/else statement for this method if there is no result?
public ActionResult Search(string q)
{
Helpers Helpers = new Helpers();
var restaurants = Helpers.getRestaurant(q);
return View("_RestaurantSearchResults", restaurants);
}
Helpers class contains:
public class Helpers
{
FoodReviewContext _db = new FoodReviewContext();
public static IQueryable<Restaurant> getRestaurant(string q)
{
var restaurents = _db.Restaurants
.Where(r => r.Name.StartsWith(q) || String.IsNullOrEmpty(q))
.Take(2);
return restaurents;
}
}
If I understand your question correctly:
public ActionResult Search(string q)
{
Helpers Helpers = new Helpers();
var restaurants = Helpers.getRestaurant(q);
if (restaurants.Count() == 0) {
// do something
} else {
// do something else
}
return View("_RestaurantSearchResults", restaurants);
}
Take a look here: http://msdn.microsoft.com/en-us/library/bb351562%28v=vs.110%29.aspx. This goes over all the methods in the IQueryable<T> interface.
Oftentimes when you are looking for how to work with a certain part of the .NET Framework, C#, or others' code, the documentation is the best first place to go to.
simply check your result count for it.
public ActionResult Search(string q)
{
Helpers Helpers = new Helpers();
var restaurants = Helpers.getRestaurant(q);
if(restaurants.Count()>0)
{
return View("_RestaurantSearchResults", restaurants);
}
else
{
ViewBag.Message="No result found";
return View("_RestaurantSearchResults");
}
}
And just check if ViewBag.Message is available or not in View.
Try this
if (restaurants.Count() != 0)
{
//non-zero thing
}
else
{
//zero thing
}

Linq To Sql using AttachAll. DuplicateKeyException

I searched for the answer on the website but I did not find one. I have the following problem ...
I'm trying to update multiple records in the database as follows:
public void SaveJItem(List<DataForDespatcher> Jitem)
{
JitemsTable.InsertAllOnSubmit(Jitem.Where(i => i.Id ==0));
JitemsTable.AttachAll(Jitem.Where(i => i.Id != 0));
JitemsTable.Context.Refresh(RefreshMode.KeepCurrentValues, Jitem);
JitemsTable.Context.SubmitChanges();
}
The table is described as follows:
[Table(Name = "tanks")]
public class DataForDespatcher
{
[Column(IsPrimaryKey = true, IsDbGenerated = true,AutoSync = AutoSync.OnInsert)]
public int Id { get; set; }
/*bla bla bla */
}
When I update I get the error:
"DuplicateKeyException" in "JitemsTable.AttachAll(Jitem.Where(i => i.Id != 0));".
How do I correctly update the data?
ADD:
razor update form:
#inherits WebViewPage<IEnumerable<DomainModel.Entities.DataForDespatcher>>
/*bla bla bla*/
data controller save(get):
public ViewResult EditForDispatcher(int group)
{
var list = DataRep.JItems.Where(x => x.group == group).Select(x => x);
return View(list);
}
data controller save(post):
[HttpPost]
public ActionResult EditForDispatcher(List<DataForDespatcher> Jitem, string addNewOperation, string sendParam, string operations)
{
if (ModelState.IsValid)
{
int group = DataRep.JItems.ToList().Max(x => x.group + 1);
if (Jitem.Any(x => x.group != 0))
foreach (var dataForDespatcher in Jitem)
dataForDespatcher.group = Jitem.Where(x=>x.group!=0).Select(x=>x.group).First();
else
foreach (var dataForDespatcher in Jitem)
dataForDespatcher.group = group;
DataRep.SaveJItem(Jitem);
}
return View(Jitem);
}
I mean exception is occur because you have both an updated and new items in List and the first in the list is not inserted item. I think, you must do somethong like this:
JitemsTable.InsertAllOnSubmit(Jitem.Where(i => i.Id ==0));
JitemsTable.AttachAll(Jitem.Where(i => i.Id != 0));
JitemsTable.Context.Refresh(RefreshMode.KeepCurrentValues, Jitem);
JitemsTable.Context.SubmitChanges();

Reuse model data in a post action

In my viewmodel, I have a list of items I fetch from the database and then send to the view. I would like to know if it's possible to avoid having to refill the options property whenever I hit a Post action and need to return the model (for validation errors and what not)?
In webforms, this wouldn't be necessary.
Edit: I was not clear. My problem is with the SelectList options I use for my DropDownLists. Everything gets posted, but if I have to return to the view (model is invalid), I have to reload the options from the database! I want to know if this can be avoided.
My viewmodel:
public class TestModel
{
public TestModel()
{
Departments = new List<SelectListItem>();
}
public string Name { get; set; }
public int Department { get; set; }
public IEnumerable<SelectListItem> Departments { get; set; }
}
My view:
#model MvcApplication1.Models.TestModel
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Name)
#Html.DropDownListFor(m => m.Department, Model.Departments)
<input type=submit value=Submit />
}
My controller (do notice the comment on HttpPost):
public ActionResult Index()
{
TestModel model = new TestModel
{
Name = "Rafael",
Department = 1,
Departments = new List<SelectListItem>
{
new SelectListItem { Text = "Sales", Value = "1" },
new SelectListItem { Text = "Marketing", Value = "2", Selected = true },
new SelectListItem { Text = "Development", Value = "3" }
}
};
// Departments gets filled from a database.
return View(model);
}
[HttpPost]
public ActionResult Index(TestModel model)
{
if (!ModelState.IsValid)
{
//Do I have to fill model.Departments again!?!?!?
return View(model);
}
else { ... }
}
Thanks in advance.
Edit: FYI, my solution was to use the Session variable.
Just need to strongly type your view, and change your controller method to have a parameter of that class type.
That is, the view
#model MyNamesspace.Models.MyModel
...
#using (Html.BeginForm())
{
....
}
And you controller method which is posted to.
[HttpPost]
public ActionResult MyAction(MyModel model)
{
...
}
EDIT: Also make sure you have form fields for each property of the model which you need posted to the controller. My example is using Razor too BTW.
I encountered a similar problem when trying to create an Order wizard in MVC (one where each page of the wizard is implemented as a partial view loaded by AJAX). I highly doubt it is the suggested method but my way of solving this was to call a custom MergeChanges method in each action called by my wizard:
public Order MergeChanges(Order newOrder)
{
var sessionHistory = (List<string>)Session["sessionHistory"];
if (sessionHistory == null || sessionHistory.Count == 0)
return MergeChanges(newOrder, -1);
return MergeChanges(newOrder, MasterViewController.GetStepNumberByName(sessionHistory.Last()));
}
public Order MergeChanges(Order newOrder, int step)
{
PreMerge(newOrder);
Order result = null;
try
{
ApplyLookups(ref newOrder);
Order oldOrder = (Order)Session["order"];
if (oldOrder == null)
{
Session["order"] = newOrder;
result = newOrder;
}
else
{
List<TypeHelper.DecoratedProperty<ModelPageAttribute>> props = null;
newOrder.GetType().GetDecoratedProperty<ModelPageAttribute>(ref props);
props = props.Where(p => (p.Attributes.Count() > 0 && p.Attributes.First().PageNumber.Contains(step))).ToList();
foreach (var propPair in props)
{
object oldObj = oldOrder;
object newObj = newOrder;
if (!string.IsNullOrEmpty(propPair.PropertyPath))
{
bool badProp = false;
foreach (string propStr in propPair.PropertyPath.Split('\\'))
{
var prop = oldObj.GetType().GetProperty(propStr);
if (prop == null)
{
badProp = true;
break;
}
oldObj = prop.GetValue(oldObj, BindingFlags.GetProperty, null, null, null);
newObj = prop.GetValue(newObj, BindingFlags.GetProperty, null, null, null);
}
if (badProp)
continue;
}
if (newObj == null)
continue;
var srcVal = propPair.Property.GetValue(newObj, BindingFlags.GetProperty, null, null, null);
var dstVal = propPair.Property.GetValue(oldObj, BindingFlags.GetProperty, null, null, null);
var mergeHelperAttr = propPair.Property.GetAttribute<MergeHelperAttribute>();
if (mergeHelperAttr == null)
{
if (newObj != null)
propPair.Property.SetValue(oldObj, srcVal, BindingFlags.SetProperty, null, null, null);
}
else
{
var mergeHelper = (IMergeHelper)Activator.CreateInstance(mergeHelperAttr.HelperType);
if (mergeHelper == null)
continue;
mergeHelper.Merge(context, HttpContext.Request, newObj, propPair.Property, srcVal, oldObj, propPair.Property, dstVal);
}
}
result = oldOrder;
}
}
finally
{
PostMerge(result);
}
return result;
}
Since my case was doing this with a wizard, only specific values applied to each page so in order to only account for properties known to the current page of the wizard, I've implemented some attributes, a (admittedly over complex) ViewController layer, and a custom validation layer. I can share some more code but the code above does the grunt work if you aren't in such a complex situation. If there is a better way, I hope to learn it from the answers to this question because this was a PITA.
I am surprised this question doesn't come up more often, and I am also surprised the obvious (IMHO) answer isn't standard practice these days: nearly all POSTs should be Ajax-based. This solves a whole slew of problems including
No need to repopulate form data when you have e.g. a validation error, or application error (exception). This is particularly desirable when you have client-side state (in true rich web application fashion).
No compulsion to perform client-side validation. Validation can be 100% server-side (where it must be anyways) and the user experience is nearly the same.
Of course, there is some initial work you need to do to build out a framework for this, for example, I have a set of AjaxUpdate, AjaxNothing, AjaxRedirect, AjaxErrors ... ActionResult types which render Json which is processed by some custom Javascript. But once you get that in place, it's smooth sailing.

Categories