Is it possible to have a generic [Bind] in c# methods? - c#

what I try to do is to bind every incomming value from my response to a string or stringlist dynamicly / generic.
So assume I would know each POST-Value of my request, e.g.
string1 = Test
string2 = Test2
I would write:
[HttpPost]
public ActionResult DoFoo(string string1, string string2)
{
}
or
[HttpPost]
public ActionResult DoFoo(string string1, [Bind(Prefix = "string2")string myString2)
{
}
My situation know is, that I have X strings with my post request. So I dont know the exact number nor the names to catch in my backend.
How to catch every given Post-value without knowing this / how to catch the values dynamicly?

I don't feel that why you have to use Prefix with BIND, when you have to bind every incoming field of response. Bind is not a good choice for that. You can use bind if you have multiple entities at the same time. Reference here
that I have X strings with my post request.
If you have to use all the fields then you can use FormCollection or Model object to receive those fields. FormCollection automatically receive all the fields from view and bind them to a collection. See this for proper example. And a code snippet is below for reference.
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
Student student = new Student();
student.FirstName = collection["FirstName"];
student.LastName = collection["LastName"];
DateTime suppliedDate;
DateTime.TryParse(collection["DOB"], out suppliedDate);
student.DOB = suppliedDate;
student.FathersName = collection["FathersName"];
student.MothersName = collection["MothersName"];
studentsList.Add(student);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
However if you have to deal with only one particular field/set of fields then you can use either Include or Exclude as per your convenience with BIND. Example shown here and code snipped is added below.
In following way you are telling that you only want to include "FirstName" of User model while receiving the form content. Everything else will be discarded.
[HttpPost]
public ViewResult Edit([Bind(Include = "FirstName")] User user)
{
// ...
}
And in following example you are telling that, please exclude "IsAdmin" field while receiving the fields. In this case, value of IsAdmin will be NULL, irrespective of any data entered/modified by end-user in view. However, in this way, except IsAdmin, data rest of the fields will be available with user object.
[HttpPost]
public ViewResult Edit([Bind(Exclude = "IsAdmin")] User user)
{
// ...
}

Related

Handling invalid request inside an Action

This is my first application in MVC.
I want to check whether list property named movies, of object model, has a value or not. So that if a user attempts to find details of a movie and that movie doesn't exist he should get an error.
Lets say, movies list only has data for two movies, but user attempts to access: /Movie/Details/3 or /Movie/Details/abc
I want to handle such invalid requests.
MovieController
public class MovieController : Controller
{
MovieCustomerViewModel model = new MovieCustomerViewModel();
// GET: Movie
public ActionResult Index()
{
model.movies = new List<Movie>
{
new Movie{id=1,name="Shrek"},
new Movie{id=1,name="Wall-e"}
};
return View(model);
}
public ActionResult Details(int? id)
{
if (id < 1 || id > 2 || !id.HasValue || model.movies.Count==0)
return HttpNotFound();
else
return View();
}
}
One simple way is to use the Count property of List, but it throws NullReferenceException.
This tells me that model has been re-instantiated and the values assigned in Index are limited to that action only. As a beginner, I don't understand what to do then..
-I can use a constructor for assigning values, but I am not sure about it. Is it a good approach?
-Can this problem be solved with Route attributes?
-Am I missing something about Action attributes or Route attributes at this stage of learning?
Thanks in advance.

Prefilling a form with long request data

I am trying to prefill some HTML form fields with data contained in the request.
My original setup, before any changes, looks like this:
Inside TicketController:
//GET: Retrieve the form
[Route("createticket")]
public ActionResult CreateTicket()
{
//Irrelevant code ommited
return View("CreateTicket");
}
//POST: Submit the form
[HttpPost]
[Route("createticket")]
public ActionResult CreateTicket(CreateTicketModel createTicketModel)
{
//Irrelevant code ommited (saving the submitted data)
return RedirectToAction("ViewTicket", new { ticketId = ticket.TicketId });
}
I could pass the data as a GET parameter, but the problem is often the data will be too long to be contained in the URL (2000+ characters).
The only solution I can currently think of is to make retrieving the form a POST instead of a GET, so I can use POST parameters to prefill the form. I have two problems with this solution:
I will have two possible POST requests at the /createticket path, which will be conflicting
It just doesn't feel right to use a POST request to retrieve a form
How could I tackle this?
Why cant you just instantiate a CreateTicket model and set the properties with the data you need to be prefilled? Via model binding the values of your properties will be set into the HTML formfields.
[Route("createticket")]
public ActionResult CreateTicket()
{
//Irrelevant code ommited
var data = GetSomeDataYouWantToPrefill();
var model = new CreateTicketModel(data);
return View("CreateTicket", model);
}
Then in the constructor of the model you can assign the values in the data object to the properties. In your view with the Html.TextboxFor method you can bind to the properties in the model

Model binder to decrypt string and convert to int

I have a model which has a property id of type int.
I pass the id in the url like Detail/20 for fetching the data. But, now my customer says they don't want to see the id, since any one can modify and see other records.
Now, I've decided to encrypt and decrypt it, and assign it to another property: encId.
public ActionResult List()
{
foreach(Employee e in empList)
{
e.encId = MyUtil.Encrypt(id,"sessionid");
}
return View(empList);
}
Finally, I make my url like Detail/WOgV16ZKsShQY4nF3REcNQ==/.
Now, all I need is to decrypt it back to the original form and assign it to the property id of type int.
public ActionResult Detail(int id) //don't want (string id)
{
}
How can I write my model binder that decrypt and convert it to valid id? Also if any error/exception occurs, it has to redirect to 404 Error page. It might happen when user manually edits some useless text in the url (encrypted id).
First, this is not the way to go about securing your website and data. Please take a look at the issues with Security Through Obscurity. You would be better off defining sets of permissions on each employee record and who can or cannot edit them. Such an example could look like this:
public ActionResult Detail(int id)
{
if(MySecurityProvider.CanView(id, HttpContext.Current.User.Identity.Name){
return View();
}
Return RedirectToAction("PermissionIssue", "Errors");
}
With that said, to continue on the path you are on, simply do the decryption within the action result.
public ActionResult Detail(string Id)
{
int actualId;
try{
actualId = MyUtil.Decrypt(id);
}catch(Exception e){
//someone mucked with my encryption string
RedirectToAction("SomeError", "Errors");
}
var employee = MyEmployeeService.GetEmployeeById(actualId);
if(employee == null){
//This was a bad id
RedirectToAction("NotFound", "Errors");
}
Return View(employee);
}

ASP.NET MVC: Ignore field(s) when editing

As I'm in the progress of learning ASP.NET MVC, I ran into a question and into some trouble
I'm trying to create a simple blog, just to test out what I have learned so far. But when it comes to editing and leaving a field i run into a problem.
I'm trying to edit an already submitted post on my blog, the post contains few fields: Id, Headline, Message, Author and Date for the submission which should not be edited, just left as it is.
Here is some code:
My post model:
namespace MyBlock.Models
{
public class Post
{
public int Id { get; set; }
[Required]
public string Author { get; set; }
[Required]
public string Headline { get; set; }
[Required]
public string Message { get; set; }
public DateTime Date { get; set; }
}
}
My edit:
[HttpGet]
public ActionResult Edit(int id = 0)
{
Post post = db.Posts.Find(id);
if (post != null) {
return View(post);
}
return HttpNotFound();
}
[HttpPost]
public ActionResult Edit(Post post)
{
if (ModelState.IsValid) {
db.Entry(post).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
And my view for edit:
#model MyBlock.Models.Post
#{
ViewBag.Title = "Edit";
}
<h2>Rediger "#Model.Headline"</h2>
#using (Html.BeginForm()) {
#Html.LabelFor(u => u.Author)
#Html.TextBoxFor(u => u.Author)
#Html.LabelFor(u => u.Headline)
#Html.TextBoxFor(u => u.Headline)
#Html.LabelFor(u => u.Message)
#Html.TextAreaFor(u => u.Message)
<input type="submit" value="Gem" />
}
I know I could throw in a #HiddenFor(u => u.Date) and the same date would be submitted. But I bet there is another way than having it as a hidden field in the source code? I mean this isn't that secure in another example? So I want something else than hidden field here. Can you guys help me out?
If I try to run this as it is. I'm getting an error which is my Date isn't set, which is logic because it want to update that one aswell. But I dont want it to. I want to leave it optional if you could say that.
Don't take candy from strangers
In other words, don't take the information from the client and directly update the DB. You should enforce your business rules on the server side and not trust the client to do it for you.
[HttpPost]
public ActionResult Edit(Post post)
{
if (ModelState.IsValid) {
var dbPost = db.Posts.FirstOrDefault(p => p.Id == post.Id);
if (dbPost == null)
{
return HttpNotFound();
}
dbPost.Author = post.Author;
dbPost.Message = post.Message;
dbPost.Headline = post.Headline;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
[HttpPost]
public ActionResult Add(Post post)
{
if (ModelState.IsValid) {
var dbPost = db.Create<Post>();
dbPost.Author = post.Author;
dbPost.Message = post.Message;
dbPost.Headline = post.Headline;
dbPost.Date = DateTime.Now(); // Don't trust client to send current date
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
In my own project I enforce rules like this at the domain layer by adding custom validation rules to the ValidateEntity method.
DateTime is a value type, and cannot be null. Thus, it can never be optional.
You need to make a it a nullable type. ie.
public DateTime? Date {get;set;}
In general, most value types in a ViewModel should be nullable, then you use Required attributes to enforce that they contain a value. This allows you to tell whether they failed to enter a value, or whether it's a default value.
In your controller, you can then check if the Date has a value with Date.HasValue and if so, then save the date.
In regards to security, in this case it's not raelly an issue. Assuming someone has access to the page (they pass authorization) and they have the right to update the date, then it doesn't matter if the user can bypass it. All they can do is submit a valid date format. Unless you want to add logic to ensure that the date is within a specific time period, then you don't have to worry. The ModelBinder will not bind to a non-valid date format.
If you want to control whether the user can update the date, say based on role, then you could add logic to your controller to check if the date has a value and the user is in the correct role, otherwise issue an error.
UPDATE:
I think the easiest solution here is to do two things. The first is to make Date nullable, as I mention above. Although this is not strictly necessary if you do not have a form field for Date in your view, if you were to add a form field later then you would get a validation error if you left the textbox empty. I like to prevent future errors from occurring if possible. Also, should someone be posting values to your Edit action manually, and they include a blank Date field, it will fail to validate, rather than simply ignore it. Making the value nullable allows the value to be completely ignored regardless of its value.
Second, is do what #p.s.w.g suggests, and only update the fields that you want updated. Retrieve the post from the database, then update all fields except Id and Date. Then call SaveChanges().
Just my 2cents here. I know this is a simple situation and the answer given is nice and straightforward. But as that list of attributes grows then it could get difficult.
So a different approuch would be along these lines
var t = _db.Blog.Where(x => x.ID == id).FirstOrDefault();
var info = typeof(Blog).GetProperties();
//properties you don't want to update
var properties = info.Where(x => x.Name != "xxx" && x.Name != "xxxx").ToList();
foreach(var p in properties)
{
p.SetValue(t, p.GetValue(temp.Volunteer));
}
_db.Entry(t).State = EntityState.Modified;
_db.SaveChanges();
But if you are just doing a few fields then the above makes sense.
Just use your noggin!

How to pass List in Redirecttoaction

I want to pass more then one parameter from RedirectToAction method
how can I pass?
My One Action Method
[HttpPost, ActionName("SelectQuestion")]
public ActionResult SelectQuestion(string email,List<QuestionClass.Tabelfields> model)
{
List<QuestionClass.Tabelfields> fadd = new List<QuestionClass.Tabelfields>();
for (int i = 0; i < model.Count; i++)
{
if (model[i].SelectedCheckbox == true)
{
List<QuestionClass.Tabelfields> f = new List<QuestionClass.Tabelfields>();
fadd.Add(model[i]);
}
}
return RedirectToAction("Question", new { email = email, model = fadd.ToList() });
}
My another Action Method
[HttpGet]
public ActionResult Question(string email,List<QuestionClass.Tabelfields> model)
{
}
I am not getting values in model.
You cannot pass a collection of complex objects in urls when redirecting.
One possibility would be to use TempData:
TempData["list"] = fadd.ToList();
return RedirectToAction("Question", new { email = email});
and then inside the Question action:
var model = TempData["list"] as List<QuestionClass.Tablefields>;
The way that I solved this problem was to serialize the list to a JSON object using the JsonConvert method from the Newtonsoft.Json nuget package. Then the serialized list can be passed as a parameter and then deserialized again to re-create the original list.
So in your SelectQuestion method you would use this code:
return RedirectToAction("Question",
new {
email = email,
serializedModel = JsonConvert.SerializeObject(fadd.ToList())
});
And in your Question method, you would use this code to deserialize the object.
[HttpGet]
public ActionResult Question(string email, string serializedModel)
{
// Deserialize your model back to a list again here.
List<QuestionClass.Tabelfields> model = JsonConvert.DeserializeObject<List<QuestionClass.Tabelfields>>(serializedModel);
}
Important, this adds the model as a query string parameter to your url, so only do this with really simple small objects, otherwise your url will be too long.
This is probably not even active anymore, but I'll leave how I did it here to maybe help someone else.
I solved this using a simple Redirect instead of a RedirectToAction:
List<int> myList = myListofItems;
var list = HttpUtility.ParseQueryString("");
myList.ForEach(x => list.Add("parameterList", x.ToString()));
return Redirect("/MyPath?" + list);
Then, on your other method:
public ActionResult Action(List<int> parameterList){}
RedirectToAction method Returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
You should either keep the data in a temporary storage like TempData / Session . TempData uses Session as the backing storage.
If you want to keep it real Stateless, you should pass an id in the query string and Fetch the List of items in your GET Action. Truly Stateless.
return RedirectToAction("Question", new { email = email,id=model.ID });
and in your GET method
public ActionResult Question(string email,int id)
{
List<QuestionClass.Tabelfields> fadd=repositary.GetTabelFieldsFromID(id);
//Do whatever with this
return View();
}
Assuming repositary.GetTabelFieldsFromID returns a List of TabelFields from the Id

Categories

Resources