Hope someone can help - this has been bugging me for around 2 hours - its probably something simple :)
Kendo UI Grid sends a request to my controller
http://localhost:1418/user/update?UserID=1&UserName=Admin&RoleName=Admin&Email=c.j.hannon%40gmail.com&Active=true&Company%5BCompanyID%5D=1&Company%5BCompanyName%5D=asd
However, the controller class 'Company' isnt bound by the binder? Can any one help my view model and controller action signature are below:
[HttpGet]
public JsonResult Update(UserViewModel model)
{
svcUser.UpdateUser(new UpdateUserRequest() {
UserID=model.UserID,
RoleID = model.RoleName,
Email = model.Email,
Active = model.Active.GetValueOrDefault(false),
UserName = model.UserName
});
return Json("", JsonRequestBehavior.AllowGet);
}
public class UserViewModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public CompanyViewModel Company { get; set; }
}
Cheers
Craig
A few things. Your immediate problem is that Company is mapped to a complex object not a primitive type. Kendo Grid just does not do this (as of this writing). Just guessing, but you probably want to setup a foreign key binding on the Grid and just pass back the Id of the company from a listbox. This is not as bad as you think and it will immediatly fix your problem and look nice too.
Maybe personal taste but seems to be a convention. Use the suffix ViewModel for the model that is bound to your View and just the suffix Model for your business objects. So a Kendo Grid is always populated with a Model.
Ex.:
public class UserModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public int CompanyID { get; set; }
}
public class CompanyModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public UserModel UserModel { get; set; }
public IList<CompanyModel> Companies { get; set; }
}
public ActionResult UserEdit(string id)
{
var model = new UserViewModel();
model.UserModel = load...
model.Companies = load list...
return View(model);
}
#model UserViewModel
...
column.ForeignKey(fk => fk.CompanyId, Model.Companies, "ID", "Name")
(Razor Notation)
BUT! This is just an example, you are better off Ajax loading the Grid with the IList becuase I assume you have many Users in the Grid at once, though you could server bind off the ViewModel with a List too. But the list of Companies is probably the same every time, so map it to the View just liek this rather than Ajax load it every time you do a row edit. (not always true)
Related
Example:
I have table Orders and table OrderPositions.
public partial class Orders
{
public Orders()
{
this.OrderPositions = new HashSet<OrderPositions>();
}
public int OrderId { get; set; }
public string Title { get; set; }
public virtual ICollection<OrderPositions> OrderPositions { get; set; }
}
public partial class OrderPositions
{
public int OrderPositionId { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public virtual Orders Orders { get; set; }
}
On the view user can modify single record from OrderPositions table.
In controller:
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
So parameter orderPosition.Orders should be = null because on the form in view user can modify only order position. But can user hack it? I mean that in parameter orderPosition.Orders won't be null and I update record not only in table OrderPositions but also in table Orders? Or ASP.NET MVC prevent from that situation?
It really depends on what you do here
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
If you're saving the whole entity then yes there is nothing stopping a user passing over addition entity properties. There are a few ways to prevent this though, here are a couple...
1.Create a new entity at the point of saving
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = orderPosition.OrderPositionId,
OrderId = orderPosition.OrderId,
Name = orderPosition.Name
};
//Then save this new entity
}
}
2.Create a Model specific to the entity's action
public class EditOrderPosition
{
[Required]
public int PositionId { get; set; }
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[HttpPost]
public ActionResult Edit(EditOrderPosition model)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = model.PositionId,
OrderId = model.Id,
Name = model.Name
};
//Then save this new entity
}
}
I generally go with the 2nd method as it stops direct user involvement with my entities. As a rule of thumb I never use entity objects as parameters in controller actions.
Hope this helps
Yes they can. This is one reason I do not expose my entities as a parameter to action methods, instead I use DTOs that only have the properties that I expect.
This is an example of the Mass Assignment Vulnerability.
Yes, there is nothing preventing a rogue app calling your endpoint with arbitrary data. Always validate everything serverside.
Is there a way in MVC to pass information from one controller to another? I have a character model that looks like this:
public class Character
{
[Key]
public string CharacterID { get; set; }
public string UserID { get; set; }
public string Name { get; set; }
public int Str { get; set; }
public int Con { get; set; }
public int Dex { get; set; }
public int Int { get; set; }
public int Wis { get; set; }
public int Cha { get; set; }
public int BaseAttack { get; set; }
}
And a separate weapon model like this:
public class Weapons
{
[Key]
public string WeaponID { get; set; }
public string UserID { get; set; }
public string CharacterID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Range { get; set; }
public int Damage { get; set; }
public int Crit { get; set; }
public int CritMultiplier { get; set; }
public string Hands { get; set; }
public string Distance { get; set; }
}
To create a weapon, you first need to create a character which assigned an ID, and I want to be able to pass that ID into the create method of my weapon controller. Is there a way to do this? Thanks
You can use TempData for this purpose. TempData stores data only between two requests. When you set the TempData the next request that is initiated can retrieve value from the TempData and it will be erased for any consequent requests.
[HttPost]
public ActionResult CreateCharacter()
{
// creates charaeters here and sets the tempdata
TempData['CharacterId'] = 50;
return RedirectToAction('CreateWeapon');
}
[HttpGet]
public ActionResult CreateWeapon()
{
var weaponModel = new WeaponModel() { CharacterId = (int)TempData['CharacterId'] };
return View(weaponModel);
}
and in your view simply have a hidden for the CharacterId, so it will be persisted if you your post fails validation or if you need to re-display the view.
#Html.HiddenFor(e => e.CharacterId);
Again this is just one approach, only if you you don't want to pass the CharacterId in the url.
You can also achive this just by passing it in the url:
[HttPost]
public ActionResult CreateCharacter()
{
// creates charaeters here and sets the tempdata
return RedirectToAction('CreateWeapon', new { characterId = 50 });
}
[HttpGet]
public ActionResult CreateWeapon(int characterId)
{
var weaponModel = new WeaponModel() { CharacterId = characterId };
return View(weaponModel);
}
I would be inclined to pass the character id to the create weapon action via routing, either as a route token that forms part of the path or via the query string. Be sure to check that the weapon can logically be associated with the character to whom the id corresponds.
You could also pass the id using TempData or Session, but considering both by default will take up memory on the web server, the simple option is to use the routing. In addition, unless you call TempData.Keep("key") after accessing TempData, the value will be removed from TempData after the first access, potentially causing issues if the user refreshes the browser window.
You could use RedirectToAction(), though as titled this will cause browser redirection.
return RedirectToAction("CreateWeapon", "Weapon", new { id = yourid });
or
#Html.ActionLink("CreateWeapon", "Create", new { id = yourid })
Edit: Your plain object property names and your action method variables need to match, to do this.
I'm trying to do something i that feels like a small task, but i cannot figure out a simple way to do it. All my approaches for doing this gets really complex for a simple task.
I have these models:
public class Blog
{
public int Id { get; set; }
public String Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public String Name { get; set; }
public int BlogId { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public String CommentText { get; set; }
public int PostId { get; set; }
public int UserProfileUserId { get; set; }
}
public class UserProfile
{
public int UserId { get; set; }
public string UserName { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
}
In the Added Comments partial view, i want to show the full user name of the user that made a comment. If i just use my base classes in my views and partial views, i get everything i need except full user name on added comments. So far, i've thought of the following ways:
ViewModels - This will result in creating a ViewModel for each of my Classes and then populate / map them manually in my controller.
Code in Views - I have the UserProfileUserId so i can just ask the repository from the view but this Kills the MVC in MVC so i don't want to do it.
Actually Adding UserProfileFirstName and UserProfileLastName to the Comment Class as foreign keys - This feels like filling the database with view specific data. It doesn't belong in a relational database.
Using regular SQL and Query the database - Just because i know SQL, this -could- be a way to do it. but then again i'm killing the MVC in MVC.
How should i do this? Where is my silly overlooked option? I've searched a lot but could not find an answer, but this could be related to me not knowing all the technical terms yet. Sorry if this is answered 1000 times before.
Ideally i would change my domain model to include a Author property of type UserProfile and load that data as well using a JOIN (Comment table and User table)
public class Comment
{
public int Id { get; set; }
public String CommentText { get; set; }
public int PostId { get; set; }
public UserProfile Author { get; set; }
}
EDIT : As per the questions in the comment
This is how i will do this.
My Repositary method will have these methods
List<Comment> GetCommentsForPost(int postId);
BlogPost GetPost(int postId);
I would have ViewModel for representing a single blog post like this
public class PostViewModel
{
public int PostID { set;get;}
public string PostText { set;get;}
public string AuthorDisplayName { set;get;}
public List<CommentViewModel> Comments { set;get;}
public PostViewModel()
{
Comments=new List<CommentViewModel>();
}
}
public class CommentViewModel
{
public int CommentID {set;get;}
public string Text { set;get;}
public string AuthorDisplayName { set;get;}
}
Now in your GET Action, Get the data from your Repositary and Map that to ViewModel and send it to view
public ActionResult ViewPost(int id)
{
var post=repositary.GetPost(id);
if(post!=null)
{
PostViewModel vm=new PostViewModel { PostID=id };
vm.PostText=post.Name;
var comments=repo.GetCommentsForPost(id);
foreach(var item in comments)
{
vm.Comments.Add(new CommentViewModel { CommentID=item.Id,
AuthorDisplayName=item.Author.FirstName});
}
return View(vm);
}
return View("NotFound");
}
Now your view will be strongly typed to The PostViewModel
#model PostViewModel
<h2>#Model.PostText</h2>
#Html.Partial("Comments",Model.Comments)
And your partial view(Comments.cshtml) will be strongly typed to a collection of CommentViewModel
#model List<CommentViewModel>
#foreach(var item in Model)
{
<div>
#item.Text
<p>Written by #item.AuthorDisplayName</p>
</div>
}
Now our views are not depending directly to Domain models. This allows us to bring data from another source tomorrow if we need (Ex :Get comments from a web service) and simply map to our view model.
Some notes
Do not add too much of code to Views. Let's keep it pure HTML as much as possible. No data access calls directly from Views!
I manually mapped the domain model to viewmodel for your understanding. You may use a mapping library like Automapper to do so. Also you may move part of the code we have in the GET action method to another servier layer so that it can be reused in multiple places.
I have a class, which has 8 properties / 8 columns in DB. In the Edit page, I want to exclude the AddedDate and UserID fields. When a user edits a voucher, he can't overwrite the AddedDate or UserID values in the DB.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime AddedDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
Here is what I have for Edit controller:
// POST: /Voucher/Edit/5
[HttpPost]
public ActionResult Edit([Bind(Exclude = "AddedDate")]Voucher voucher)
{
if (ModelState.IsValid)
{
db.Entry(voucher).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(voucher);
}
On Edit page, when I click on submit, I got the following error:
System.Data.SqlServerCe.SqlCeException: An overflow occurred while converting to datetime.
Seems like the AddedDate didn't get excluded from the voucher object and triggered the error.
Would you please let me know how to fix it? Thanks!
(it is an updated version of asp.net mvc3 UpdateModel exclude properties is not working, I will go with another approach)
Never use your domain entities as action arguments and never pass your domain entities to your views. I would recommend you to use view models. In the view model you will include only the properties that you want to be bound from the view. The view model is a class that's specifically tailored to the requirements of a given view.
public class VoucherViewModel
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
}
and then:
[HttpPost]
public ActionResult Edit(VoucherViewModel model)
{
// TODO: if the view model is valid map it to a model
// and pass the model to your DAL
// To ease the mapping between your models and view models
// you could use a tool such as AutoMapper: http://automapper.org/
...
}
UPDATE:
In the comments section #Rick.Anderson-at-Microsoft.com points out that while I have answered your question I haven't explained where the problem comes from.
The thing is that DateTime is a value type meaning it will always have a value. The [Bind(Exclude = "AddedDate")] works perfectly fine and it does what it is supposed to do => it doesn't bind the AddedDate property from the request. As a consequence the property will have its default value which for a DateTime field is 1/1/0001 12:00:00 AM and when he attempts to save this in SQL Server it blows because SQL Server doesn't support such format.
I am developing a application for Sales Order Management using ASP.NET MVC 3.0. I need to develop a page where Customer Details can be added.
Customer Details Include
public class Customer
{
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Alias { get; set; }
public int DefaultCreditPeriod { get; set; }
public Accounts Accounts { get; set; }
public IList<Address> Addresses { get; set; }
public IList<Contact> Contacts { get; set; }
}
public class Accounts
{
public int ID { get; set; }
public string VATNo { get; set; }
public string CSTNo { get; set; }
public string PANNo { get; set; }
public string TANNo { get; set; }
public string ECCNo { get; set; }
public string ExciseNo { get; set; }
public string ServiceTaxNo { get; set; }
public bool IsServiceTaxApplicable { get; set; }
public bool IsTDSDeductable { get; set; }
public bool IsTCSApplicable { get; set; }
}
public class Address
{
public int ID { get; set; }
public AddressType Type { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string Line4 { get; set; }
public string Country { get; set; }
public string PostCode { get; set; }
}
public class Contact
{
public int ID { get; set; }
public ContactType Type { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string PhoneNumber { get; set; }
public string Extension { get; set; }
public string MobileNumber { get; set; }
public string EmailId { get; set; }
public string FaxNumber { get; set; }
public string Website { get; set; }
}
Customer Requires a single page to fill all the customer details(General info, Account Info,Address Info and Contact Info). There will be multiple Addresses(Billing, Shipping, etc) and multiple Contacts (Sales, Purchase). I am new to MVC. How to Create the View for the above and Add multiple Address dynamically?
I often create wrapper models to handle this kind of situation e.g.
public class CustomerWrapperModel
{
public Customer Customer { get; set;}
public Accounts Accounts { get; set;}
public List<Address> AddressList { get; set}
//Add
public CustomerWrapperModel()
{
}
//Add/Edit
public CustomerWrapperModel(Customer customer, Accounts accounts, List<Address> addressList)
{
this.Customer = customer;
this.Accounts = accounts;
this.AddressList = addressList;
}
}
then declare the View to be of type CustomerWrapperModel and use editors like so:
#model MyNamespace.CustomerWrapperModel
#Html.EditorFor(model => model.Customer)
#Html.EditorFor(model => model.Accounts)
#Html.EditorFor(model => model.AddressList)
and have a controller to receive the post that looks like this:
[HttpPost]
public ActionResult(Customer customer, Accounts accounts, List<Address> addressList)
{
//Handle db stuff here
}
As far as adding addresses dynamically I found the best way to do this if you're using MVC validation and want to keep the list structured correctly with the right list indexes so that you can have the List parameter in your controller is to post the current Addresses to a helper controller like this:
[HttpPost]
public PartialResult AddAddress(List<Address> addressList)
{
addressList.Add(new Address);
return PartialView(addressList);
}
then have a partial view that just renders out the address fields again:
#model List<MyNamespace.Address>
#{
//Hack to get validation on form fields
ViewContext.FormContext = new FormContext();
}
#Html.EditorForModel()
make sure you address fields are all in one container and then you can just overwrite the existing ones with the returned data and your new address fields will be appended at the bottom. Once you have updated your container you can do something like this to rewire the validation:
var data = $("form").serialize();
$.post("/Customer/AddAddress", data, function (data) {
$("#address-container").html(data);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
});
NB. I know some people with have an issue with doing it this way as it requires a server side hit to add fields to a page that could easily just be added client side (I always used to do it all client side but tried it once with this method and have never gone back). The reason I do it this way is because it's the easiest way to keep the indexes on the list items correct especially if you have inserts as well as add and your objects have a lot of properties. Also, by using the partial view to render the data you can ensure that the validation is generated on the new fields for you out of the box instead of having to hand carve the validation for the newly added client side fields. The trade off is in most cases a minor amount of data being transferred during the ajax request.
You may also choose to be more refined with the fields you send to the AddAddress controller, as you can see I just post the entire form to the controller and ignore everything but the Address fields, I am using fast servers and the additional (minor) overhead of the unwanted form fields is negligible compared to the time I could waste coding this type of functionality in a more bandwidth efficient manner.
You pass your root model object to the View call in your controller like this:
public ActionResult Index() {
var customer = GetCustomer(); // returns a Customer
return View(customer);
}
And then your view looks something like this:
#model Customer
<!DOCTYPE html>
<!-- etc., etc. -->
<h1>Customer #Model.Name</h1>
<ul>
#foreach (var address in Model.Addresses) {
<li>#address.Line1</li>
}
</ul>
One gets the picture.
The code above depends on the #model directive, which is new in ASP.NET MVC 3 (see this blog post).
Is a good question :D for normal navigation properties such as Accounts doing this is not to hard:
#Html.EditorFor(model => model.Accounts.ID)
#Html.EditorFor(model => model.Accounts.VATNo)
will do something you want. But for collection navigation properties (Addresses and Contacts) you can't do this in one place by default. I suggest you use a different page for Addresses (and one for Contacts). Because it is the easiest way. But if you want to do this in one place (and also with out AJAX requests), you can create view by Customer, use scaffolding for model and it's simple navigation properties, and for lists (Addresses, Contacts) you must add them with JavaScript to the input fields (for example for each Address added, put it in an Array) and post fields to server. At server you can get main model and simple properties by default model-binder and for lists, you can 1) create your own model binder 2) parse them from inputted strings by yourself. Good lock