MVC5 - How to know if an action input was completely empty - asp.net-mvc

I have an action that takes a complex object as an input. I want to be able to populate any of the values with either POST data, or from the query string in a GET request. This works fine.
I also want to provide a default value if no user input was provided, however, this is not working because filter is never null, even if there were no querystring params from a GET request. What happens instead is, MVC just calls the model's default constructor without setting any properties instead of giving me a null.
public ActionResult Index(DataFilterInput filter = null)
{
if (filter == null)
filter = new DataFilterInput { Top = 100 };
var model = new IndexModel();
return View(model);
}
How can I know whether I should be defaulting the values in the absence of user input (I do not want to go into the Request query string or form collections)?

to provide a default value for your object this sample would work
public class SearchModel
{
public bool IsMarried{ get; set; }
public SearchModel()
{
IsMarried= true;
}
}
and if you want to validate the model
public ActionResult Index(DataFilterInput filter = null)
{
if (!ModelState.Isvalied)
filter = new DataFilterInput { Top = 100 };
var model = new IndexModel();
return View(model);
}

You can decare top nullable
public int? Top {get;set;}
So when no top value is provided it will be null by default and you can check it by using ==null or HasValue like this
public ActionResult Index(DataFilterInput filter )
{
if (!filter.Top.HasValue )
filter = new DataFilterInput { Top = 100 };
var model = new IndexModel();
return View(model);
}

Related

ASP.NET Core Capturing POST data

I have an Openlayer's map interface where I'm capturing the user's adding new points to the map. What I want is to take those location data points and save them do a database. So I have a working function on the .cshtml page that looks like this:
map.on('dblclick', function (evt) {
var coordinate = evt.coordinate;
var datapoints = new Array();
var features = source.getFeatures();
for (var i = 0; i < features.length; i++) {
var poi = features[i];
var datapt = new Object();
datapt.X = poi.values_.geometry.flatCoordinates[0];
datapt.Y = poi.values_.geometry.flatCoordinates[1];
datapoints.push(datapt);
}
var xhr = new XMLHttpRequest();
xhr.open("POST", "Draw_Features", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(datapoints));
});
This seems to work just fine and is sending back an encoded JSON of all the locations. In my Controller file, I have the following:
[HttpGet]
public IActionResult Draw_Features()
{
return View();
}
[HttpPost]
public IActionResult Draw_Features(string someValue)
{
//TODO
return View("Index");
}
[HttpPost]
public IActionResult AddPointsToDB(string someValue)
{
//TODO
return View("Index");
}
I have two problems:
1) I want to return the data to the "AddPointsToDB()" function but it is instead going to the "Draw_Features()" one instead. How do I specify in the xhr.send() the correct landing function?
2) I was expecting the function to receive the JSON via the 'string someValue' variable. But that variable comes in NULL. What is the correct way to access that JSON from within the function?
Thanks!
EDIT: Fixed the JSON convert code which was bombing. Still the same questions...
EDIT 2: Showing POST data from Chrome
For a working demo, follow steps below:
Define a new model to receive the data instead of string input. For passing datapoints to controller action, it will be serialize and de-serialize by MVC Built-in function.
public class Coordinate
{
public double X { get; set; }
public double Y { get; set; }
}
Change Action with [FromBody]IList<Coordinate> coordinates which will specify the modele binding to read the model from body.
public IActionResult AddPointsToDB([FromBody]IList<Coordinate> coordinates)
{
//TODO
return View("Index");
}
As already point out, make sure you set the xhr.open("POST", "AddPointsToDB", true); with the action name you want.

MVC Model Binding Post Values Best Practice

I'm trying to figure out if what I'm doing is flawed or acceptable. Specifically, I'm questioning the NULL value I'm getting back in the POST to Controller in 'Timeframes' property. The 'Timeframe' (singular) property DOES contain the value so all is good. However, is this just how model binding works and the property (Timeframes) that is used to populate the DDL comes back as null? Is this best practice and what I'm doing is fine? Is this a concern of sending values around that are not needed...performance concern?
Timeframe = used to return value back to Controller on Post
Timeframes = used to populate DDL values
Drop Down List Box on View:
#Html.DropDownListFor(m => m.Timeframe, Model.Timeframes)
Model:
public class ABCModel
{
public List<SelectListItem> Timeframes { get; set; }
public string Timeframe { get; set; }
}
Controller:
[HttpPost]
public void TestControllerMethod(ABCModel model)
{
//this value is null.
var timeFrames = model.Timeframes;
//this value is populated correctly
var timeFrame = model.Timeframe;
}
A form only posts back the name/value pairs of its successful controls. You have created a form control for property Timeframe, so you get the value of the selected option in the POST method.
You have not (and should not), created form controls for each property of each SelectListItem in your Timeframes property, so nothing relating to it is send in the request when the form is submitted, hence the value of Timeframes is null.
If you need to return the view because ModelState is invalid, then you need to re-populate the TimeFrames property as you did in the GET method (otherwise your DropDownListFor() will throw an exception). A typical implementation migh look like
public ActionResult Create()
{
ABCModel model = new ABCModel();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(ABCModel model)
{
if (!modelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Save and redirect
}
private void ConfigureViewModel(ABCModel model)
{
model.TimeFrames = ....; // your code to populate the SelectList
}

MVC 3 accept null foreach in the viewModel

I have a page where users can enter their state information and then a list of other users come back within the state. I am using a foreach loop.
Some of the states have 0 users, which then leads me to get an error: Object reference not set to an instance of an object. How can I get past that error? The particular model I'm using is called Profiles.
The Model:
public class homepage
{
public List<profile> profile { get; set; }
public PagedList.IPagedList<Article> article { get; set; }
}
The Controller:
public ActionResult Index()
{
HttpCookie mypreference = Request.Cookies["cook"];
if (mypreference == null)
{
ViewData["mypreference"] = "Enter your zipcode above to get more detailed information";
var tyi = (from s in db.profiles.OrderByDescending(s => s.profileID).Take(5) select s).ToList();
}
else
{
ViewData["mypreference"] = mypreference["name"];
string se = (string)ViewData["mypreference"];
var tyi = (from s in db.profiles.OrderByDescending(s => s.profileID).Take(5) where se==s.state select s).ToList();
}
return View();
}
The View:
#if (Model.profile != null)
{
foreach (var item in Model.profile)
{
#item.city
}
}
When I get the Object reference not set to an instance of an object error, the line #if (Model.profile != null) is highlighted, so I tried to do this:
public List<profile>? profile { get; set; }
But it didn't work. Any ideas of how to accept an empty Model in a foreach or just skip the code at runtime?
Profile is a list. See if the list has any elements.
See if this works:
#if (Model.profile.Any())
{
foreach (var item in Model.profile)
{
#item.city
}
}
Just noticed, you're calling View() but not passing it a model, then in the view you're referencing Model.profile. Inevitably Model is null, and therefore has no profile property to access. Make sure you're passing the model off to the view in the return View(model) call.
Follow-up for collections
I've always found that any time you have variable that implements IEnumerable<T>, it's best to populate it with an empty set over a null value. That is to say:
// no-nos (IMHO)
IEnumerable<String> names = null; // this will break most kinds of
// access reliant on names being populated
// e.g. LINQ extensions
// better options:
IEnumerable<String> names = new String[0];
IEnumerable<String> names = Enumerable.Empty<String>();
IEnumerable<String> names = new List<String>();
Unless you like checking if (variable != null && variables.Count() > 0) every time you want to access it, make it an empty collection and leave it at that.
To come full-circle, as long as the variable is populated with a collection of some sort (empty or populated) a foreach shouldn't break. it will simply skip past the code block and not output anything. If you're getting an object null error, it's most likely because the variable is empty and the enumerator could not be retrieved.

Survey Controller POST method problems ASP.NET MVC

I'm attempting to create a single Controller class to handle all foreseeable surveys that I'll end up creating in the future. Currently I have a 'Surveys' table with fields: Id, SurveyName, Active. On the 'master' Surveys' Index page I list out every SurveyName found in that table. Each SurveyName is clickable, and when clicked on, the page sends the SurveyName as a string to the receiving controller action. Said controller action looks like this:
//
//GET: /Surveys/TakeSurvey/
public ActionResult TakeSurvey(string surveyName)
{
Assembly thisAssembly = Assembly.GetExecutingAssembly();
Type typeToCreate = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
object newSurvey = Activator.CreateInstance(typeToCreate);
ViewBag.surveyName = surveyName;
return View(surveyName, newSurvey);
}
Using reflection I am able to create a new instance of the type (Model) designated by the passed-in string 'surveyName' and am able to pass that Model off to a view with the same name.
EXAMPLE
Someone clicks on "SummerPicnic," the string "SummerPicnic" is passed to the controller. The controller, using reflection, creates a new instance of the SummerPicnic class and passes it to a view with the same name. A person is then able to fill out a form for their summer picnic plans.
This works all fine and dandy. The part that I'm stuck at is trying to save the form passed back by the POST method into the correct corresponding DB table. Since I don't know ahead of time what sort of Model the controller will be getting back, I not only don't know how to tell it what sort of Model to save, but where to save it to, either, since I can't do something ridiculous like:
//
//POST: Surveys/TakeSurvey
[HttpPost]
public ActionResult TakeSurvey(Model survey)
{
if (ModelState.IsValid)
{
_db. + typeof(survey) + .Add(survey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
Is there a way to do this, or should I go about this from a whole different angle? My ultimate goal is to have a single Controller orchestrating every simple-survey, so I don't have to create a separate controller for every single survey I end up making down the road.
An alternative solution I can think of is to have a separate method for every survey, and to have which method to call defined inside of every survey's view. For example, if I had a SummerPicnic survey, the submit button would call an ActionMethod called 'SummerPicnic':
#Ajax.ActionLink("Create", "SummerPicnic", "Surveys", new AjaxOptions { HttpMethod = "POST" })
A survey for PartyAttendance would call an ActionMethod 'PartyAttendance,' etc. I'd rather not have to do that, though...
UPDATE 1
When I call:
_db.Articles.Add(article);
_db.SaveChanges();
This is what _db is:
private IntranetDb _db = new IntranetDb();
Which is...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace Intranet.Models
{
public class IntranetDb : DbContext
{
public DbSet<Article> Articles { get; set; }
public DbSet<ScrollingNews> ScrollingNews { get; set; }
public DbSet<Survey> Surveys { get; set; }
public DbSet<Surveys.test> tests { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
You can try something like this,
UPDATE:
The built-in UpdateModel will work with generic model see this post, so we got little more work.
[HttpPost]
public ActionResult TakeSurvey(FormCollection form, surveyName)
{
var surveyType = Type.GetType(surveyName);
var surveyObj = Activator.CreateInstance(surveyType);
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => surveyObj, surveyType),
ModelState = ModelState,
ValueProvider = form
};
binder.BindModel(ControllerContext, bindingContext);
if (ModelState.IsValid)
{
// if "db" derives from ObjectContext then..
db.AddObject(surveyType, surveyObj);
db.SaveChanges();
// if "db" derives from DbContext then..
var objCtx = ((IObjectContextAdapter)db).ObjectContext;
objCtx.AddObject(surveyType, surveyObj);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
Check this two know the diff between DbContext and ObjectContext
I ended up with a slightly modified version of Mark's code:
[HttpPost]
public ActionResult TakeSurvey(string surveyName, FormCollection form)
{
//var surveyType = Type.GetType(surveyName);
//var surveyObj = Activator.CreateInstance(surveyType);
// Get survey type and create new instance of it
var thisAssembly = Assembly.GetExecutingAssembly();
var surveyType = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
var newSurvey = Activator.CreateInstance(surveyType);
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => newSurvey, surveyType),
ModelState = ModelState,
ValueProvider = form
};
binder.BindModel(ControllerContext, bindingContext);
if (ModelState.IsValid)
{
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
objCtx.AddObject(surveyName, newSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
I was running into surveyType being 'null' when it was set to Type.GetType(surveyName); so I went ahead and retrieved the Type via Reflection.
The only trouble I'm running into now is here:
if (ModelState.IsValid)
{
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
objCtx.AddObject(surveyName, newSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
When it tries to AddObject I'm getting the exception "The EntitySet name 'IntranetDb.test' could not be found." I just need to figure out to strip off the prefix 'IntranetDb.' and hopefully I'll be in business.
UPDATE
One thing I completely overlooked was passing the Model to the controller from the View...oh bother. I currently have an ActionLink replacing the normal 'Submit' button, as I wasn't sure how else to pass to the controller the string it needs to create the correct instance of Survey model:
<p>
#Ajax.ActionLink("Create", "TakeSurvey", "Surveys", new { surveyName = ViewBag.surveyName }, new AjaxOptions { HttpMethod = "POST" })
#*<input type="submit" value="Create" />*#
</p>
So once I figure out how to turn 'IntranetDb.test' to just 'test' I'll tackle how to make the Survey fields not all 'null' on submission.
UPDATE 2
I changed my submission method from using an Ajax ActionLink to a normal submit button. This fixed null values being set for my Model values after I realized that Mark's bindingContext was doing the binding for me (injecting form values onto the Model values). So now my View submits with a simple:
<input type="submit" value="Submit" />
Back to figuring out how to truncate 'IntranetDb.test' to just 'test'...
Got It
The problem lies in my IntranetDb class:
public class IntranetDb : DbContext
{
public DbSet<Article> Articles { get; set; }
public DbSet<ScrollingNews> ScrollingNews { get; set; }
public DbSet<SurveyMaster> SurveyMaster { get; set; }
public DbSet<Surveys.test> tests { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
objCtx.AddObject(surveyName, newSurveyEntry); was looking for an entry (an "EntitySet") in the IntranetDb class called "test." The problem lies in the fact that I don't have an EntitySet by the name of "test" but rather by the name of "tests" with an 's' for pluralization. Turns out I don't need to truncate anything at all, I just need to point to the right object :P Once I get that straight I should be in business! Thank you Mark and Abhijit for your assistance! ^_^
FINISHED
//
//POST: Surveys/TakeSurvey
[HttpPost]
public ActionResult TakeSurvey(string surveyName, FormCollection form)
{
//var surveyType = Type.GetType(surveyName);
//var surveyObj = Activator.CreateInstance(surveyType);
// Create Survey Type using Reflection
var thisAssembly = Assembly.GetExecutingAssembly();
var surveyType = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
var newSurveyEntry = Activator.CreateInstance(surveyType);
// Set up binder
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => newSurveyEntry, surveyType),
ModelState = ModelState,
ValueProvider = form // Get values from form
};
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
// Retrieve EntitySet name for Survey type
var container = objCtx.MetadataWorkspace.GetEntityContainer(objCtx.DefaultContainerName, DataSpace.CSpace);
string setName = (from meta in container.BaseEntitySets
where meta.ElementType.Name == surveyName
select meta.Name).First();
binder.BindModel(ControllerContext, bindingContext); // bind form values to survey object
if (ModelState.IsValid)
{
objCtx.AddObject(setName, newSurveyEntry); // Add survey entry to appropriate EntitySet
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
It's kind of bloated but it works for now. This post helped me get the EntitySet from the Survey object itself so I didn't need to worry about establishing some sort of EntitySet naming convention.
The main problem I see is to bind to the model to the TakeSurvey POST method. If you want different types of survey models should be handled by this method and MVC should bind to this model before calling the action, I believe you can have a wrapper model class over all such generic model, say SurveyModel and use custom model binder to bind to these models.
public class SurveyModel
{
public string GetSurveyModelType();
public SummerPicnicSurvey SummerPicnicSurvey { get; set; }
public PartyAttendanceSurvey PartyAttendanceSurvey { get; set; }
}
Then write a custom mobel binder to bind this model. From the request form fields we can see what type of survey model is posted and then accordingly fetch all the fields and initialize the SurveyModel class. If SummerPicnicSurvey is posted then class SurveyModel will be set with this class and PartyAttendanceSurvey will be null. Example custom model binder.
From the controller action TakeSurvey POST method, You can update db like this:
[HttpPost]
public ActionResult TakeSurvey(SurveyModel survey)
{
if (ModelState.IsValid)
{
if(survey.GetSurveyModelType() == "SummerPicnicSurvey")
_db.UpdateSummerPicnicSurvey(survey.SummerPicnicSurvey);
else if (survey.GetSurveyModelType() == "PartyAttendanceSurvey")
_db.UpdateSummerPicnicSurvey(survey.PartyAttendanceSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
Instead of SurveyModel encapsulating the other surveys you can have inheritance and use .net as to typecast with a check and use the Model.
Having said this, I think there is no harm in using different methods for each model. This will enable you to unit test the code well. Too many if else is not healthy to maintain. Or you can transfer the generic model SurveyModel to the repository or data access layer and let it handle that in a polymorphic way. I would prefer more small functions and keep the code clean.
Edit: The inheritance way:
public class SurveyModel
{
public virtual bool Save();
}
public partial class SummerPicnicSurvey : SurveyModel
{
public bool Save(SummerPicnicSurvey survey)
{
using(var _dbContext = new MyContext())
{
_dbContex.SummerPicnicSurveys.Add(survey);
_dbContex.SaveChanges();
}
}
}
[HttpPost]
public ActionResult TakeSurvey(SurveyModel survey)
{
if (ModelState.IsValid)
{
survey.Save();
return RedirectToAction("Index", "Home");
}
return View();
}
Any new Survey model type you add has to implement the SaveChanges or Save method, Which would call the proper dbcontext method. The controller action would just call Save on the generic `SurveyModel' reference passed to it. Thus the action will be closed for modification but open for modification. The open-close design principle.

MVC: I am using MultiSelectList and when I select two values it just sends one value?

I using MVC-Viewmodel in my project, my problem is that even if i CTRL-click two values in my listbox It only grabs one value. I want users to be able to select two values but I dont know why it doesnt happen any tips is appreciated!
Here is my GET n POST action inside my controller:
public ActionResult Create()
{
CreateViewModel model = new CreateViewModel();
List<CoreValue> corevalues = Arep.getallC();
model.CoreValuess = new MultiSelectList(corevalues, "CID", "Cname");
return View(model);
}
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
if (ModelState.IsValid)
{
Question q = new Question();
var CoreValueID = int.Parse(model.Cname);
var getallC = Arep.getbycid(CoreValueID);
q.CoreValue.Add(getallC);
q.QuestionText = model.QuestionText;
Arep.addquestion(q);
Arep.save();
return RedirectToAction("Index");
}
return View(model);
This is inside my CreateViewModel:
public MultiSelectList CoreValues { get; set; }
And this is inside my View:
#Html.ListBoxFor(model => model.Cname,Model.Corevaluess)
What seem to be the problem?
Thanks in Advance!
Best Regards
Spelling errors aside, I believe the following is why this is failing:
In your ListBoxFor method, you are using model.Cname. By this, I believe you mean "choose the cName of selected CoreValues". However (and I'm guessing because I can't see your model), the Cname property on the CreateViewModel is of type string. Because of this, you are only ever going to have one value at a time. You need a property that is of type IEnumerable in order to hold multiple selections.
Update your model to the following:
public class CreateViewModel
{
public IEnumerable<string> SelectedValues { get; set; }
public IEnumerable<CoreValue> CoreValues { get; set; }
}
SelectedValues will be used to contain the selected values on the post. We can also add items to it to signify what should be automatically selected when the view is created.
In your controller do the following:
public ActionResult Create()
{
CreateViewModel model = new CreateViewModel();
model.CoreValues = Arep.getallC();
return View(model);
}
Lastly, update the view:
#Html.ListBoxFor(m => m.SelectedValues, new MultiSelectList(Model.CoreValues, "CID", "Cname"))
Now, whenever you post, you should be able to see the values that a user selected.
EDIT: I'm not completely sure what some of your methods do so I'm taking a guess.
The POST method for Create:
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
if (ModelState.IsValid)
{
foreach(var CoreValueID in model.SelectedValues)
{
Question q = new Question();
var getallC = Arep.getbycid(CoreValueID);
q.CoreValue.Add(getallC);
q.QuestionText = model.QuestionText;
Arep.addquestion(q);
}
Arep.save();
return RedirectToAction("Index");
}
return View(model);
}
you would need to pass selectedvalues as below
List<CoreValue> selectedvalues = Null;
model.CoreValuess = new MultiSelectList(corevalues, "CID", "Cname",selectedvalues);
return View(model);
Refer MSDN link and this helpful Article
As stated above, the ASP.Net Mvc wants to have a list of string, but the same thing can be achieved by using the classic ASP style
Request.Form["CoreValues"]
this will provide as comma separated values.

Resources