MVC structure query - c#

Evening all, I'm chasing my tail trying to get the structure of a simple ASP.NET MVC correct. From the start I should say I'm completely new to ASP and MVC, I've used a bit C# before.
For examples sake let's say I'm trying to check two pieces of information e.g. a username and password that a user enters, against a database that stores all the user info. If they put in the correct credentials a summary of the user info is displayed, if not they are taken back (well visually never leave) the login page with a please try again message.
So I have my Home (Login page) view, a HomeController and a HomeIndexViewModel. Similarly I have an Account view, a AccountController and an AccountUserViewModel (there is also AccountIndexViewModel but that isn't really used).
The Home view 'takes in' (through it's controller) a HomeIndexVM as it's model:
#model ViewModels.HomeIndexViewModel
#using (Html.BeginForm("User", "Account", FormMethod.Post))
{
if (#Model.PreviousAttempts)
{
<p><b>Username or password were not recognised please try again.</b></p>
}
<p>Username: #Html.TextBoxFor(x => x.Username)</p>
<p>Password: #Html.PasswordFor(x => x.Password)</p>
<input id="btnLogin" type="submit" value="Login" />
}
HomeController:
public class HomeController : Controller
{
public ActionResult Index(bool invalidLogin = false)
{
var vm = new HomeIndexViewModel() { Username = string.Empty, Password = string.Empty, PreviousAttempt = invalidLogin };
return View(vm);
}
}
And finally the HomeIndexViewModel.cs
public class HomeIndexViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public bool PreviousAttempt { get; set; }
}
I think that is okay so far. Now on clicking the login button, it will post to Account, User.
public ActionResult User(UserLogin userLogin)
{
if (!ModelState.IsValid)
return RedirectToAction("Index", "Home", new { invalidLogin = true });
// Match username and password against database
User user = userLogin.IsValid(userLogin.Username, userLogin.Password);
if (user != null)
{
return this.View(user);
}
return RedirectToAction("Index", "Home", new { invalidLogin = true });
}
There is a couple of things here, you can see the redirects back to the login page with the true flag to show the failed login message.
But more to my point, clearly it won't work as it takes a UserLogin as a parameter. This is a model object that contains:
public class UserLogin
{
private NewDBSolution_v1Entities accountsDB = new NewDBSolution_v1Entities();
[Required, MinLength(2)]
public string Username { get; set; }
[DataType(DataType.Password), Required]
public string Password { get; set; }
public bool PreviousAttempts { get; set; }
public User IsValid(string username, string password) // Ideally use the local username and password properties rather than pass in as they are the same.
{
// Match username and password against database and return full user info if match found, otherwise return null
}
}
So what am I asking... well is it best practice for, in this case, the Account's User Action to take the HomeIndexViewModel, even although it's Home as opposed to Account related? Or should I pass in a model object as I had originally done, and use it to the validation (what I don't like about this is the validation is done in object that is passed from a different view if that makes sense?)
How do you best bundle information up from a view to pass to an Action? I realize VMs and Model objects the compiler doesn't care they are just classes but I'd like to get my separation of concerns correct.
Basically here all I need is the username and password from the Home view, should that be bundled in to a VM or a M?
It just seems to me there is potential for an awful lot of classes that are just slightly different, so why not make one and use it. I suppose that is where inheritance comes in, but do you gain much there if ever subclass just adds one different property?
Anyway I keep going in circles about the best way to structure. I did read somewhere that VMs should basically be a mask/adapter over the model such that the view only sees just what it needs. But these VMs don't have a model associated with them.
I'm rambling now, if anyone can make head or tail of this and give me a few pointers I'd be very grateful, thanks.

I think you might be going round in circles because you've created the login view in the HomeController. Login code is Account-related, so why not put this into the AccountController instead?
I tend to favour posting to the same action as you get from, if you follow the Post-Redirect-Get pattern, as you're trying to do here. I would move the login action into the AccountController. I would move the code that does the database check into a separate class, rather than keeping it in the model. Perhaps a membership provider or something like that. I would pass that provider into the Controller - this allows the Controller and the Model object to stop worrying about how to decide whether a user is valid or not. They can just ask the provider to tell them.
It's generally good practice to keep your entities (whether UI or domain) simple. They certainly should have database connection objects inside them.
Your Controller would look something like this:
public class AccountController : Controller
{
private readonly IMembershipProvider membershipProvider;
public AccountController(IMembershipProvider membershipProvider)
{
this.membershipProvider= membershipProvider;
}
public ActionResult Login()
{
var viewModel = new LoginViewModel();
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
var user = membershipProvider.Find(viewModel.Username, viewModel.Password);
if (user != null)
{
membershipProvider.SignIn(user, true);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("Login", "Your credentials were not recognised. Please try again.");
return View(viewModel);
}
}
Because the Login action has the same name for both Get and Post, your view becomes simpler. You'd have a Login.cshtml view in the Views/Account folder like this:
#model ViewModels.LoginViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken() // good practice to add this, ties in with the ValidateAntiForgeryToken attribute on the Post action
#Html.ValidationSummary() // displays model errors if there are any
<p>Username: #Html.TextBoxFor(x => x.Username)</p>
<p>Password: #Html.PasswordFor(x => x.Password)</p>
<input id="btnLogin" type="submit" value="Login" />
}
To answer this specific question:
How do you best bundle information up from a view to pass to an Action?
You let the default model binder take care of that if you can. It's very powerful and can handle most situations in regards to data being passed back from a form. All the model binder does is match name-value pairs from the form collection that is passed back when the form is posted to properties on your class. Provided your class has the string properties Username and Password the model binder will populate them. The actual class that you provide is irrelevant as far as the model binder is concerned. This would work equally well (but anyone else working on the project wouldn't thank you for it!):
public class Dave
{
[Required, MinLength(2)]
public string Username { get; set; }
[DataType(DataType.Password), Required]
public string Password { get; set; }
}
And then in the Post action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(Dave dave)
{
...
}
Does that help?

Related

How to properly preserve view rendering data on model validation failure?

In my application, I have a form where a user can add a bank account to their account. I've implemented this through two actions on my controller, namely:
public async Task<IActionResult> Add()
{
var client = await _clientFactory.CreateClientAsync();
// We obtain the available account types from a remote service.
var accountTypes = await client.GetAccountTypesAsync();
var viewModel = new AddAccountViewModel
{
AccountTypes = accountTypes;
}
return View(viewModel);
}
[HttpPost]
public async Task<IActionResult> Add(AddAccountViewModel model)
{
if (!ModelState.IsValid)
return View(model);
// Store the account for the user, and redirect to a result page.
}
My AddAccountModel currently looks like this, and has two purposes:
public class AddAccountViewModel
{
public List<AccountTypeModel> AccountTypes { get; set; }
[Required]
[StringLength(100)]
public string AccountName { get; set; }
[Required]
[Range(1, int.MaxValue]
public decimal CurrentBalance { get; set; }
[Required]
public Guid AccountTypeId { get; set; }
}
One the one hand, the AccountTypes property is used to populate a list in the view:
#{
var accountTypeList = new SelectList(accountTypes, "Id", "Name");
}
<select class="form-control" id="accountType" asp-for="AccountTypeId" asp-items="#accountTypeList">
</select>
This all works fine when I arrive on the initial page via GET /Add. The form renders, the select box is populated with the items from the service, as I'd expect.
However, the form also includes validation, and should a user input invalid data or leave fields empty, POST /Add is called, passed the model, runs validation, and ultimately redirects the user back to GET /Add, with the specific reasons as to why validation failed. This is where the problem arises.
When the user is redirected back to the form and asked to correct their input, AccountTypes in my model is null. Which makes sense, as we didn't send it as a part of the POST request - it only serves as input for the form to render. With the form not knowing the account types, the user will never be able to correct their input, as they won't be able to select a valid account type.
As mentioned above, my model is two-fold: on the one hand it contains data the view requires to render, and on the other hand it contains data the user provides and is then processed by the application.
Given the above, my question is: how should I properly preserve the input data required for my view to render when model validation failed? I.e., is my ViewModel the correct place to keep the collection of AccountTypeModels, or should I be keeping them in ViewData, or perhaps even TempData?
Currently, both my application and services are stateless, which is why I didn't go down the "just stick it in TempData" route yet, as from what I can gather, TempData is stored in the server's session. I also don't want to query the service for the available account types at the start of the view if I can avoid it, as it seems to violate MVC's separation of concerns.
Thanks for reading, and I hope someone can help me out with this.
You need to repopulate the AccountTypes collection in the POST method before you return the view. I would recommend that your view model contain a property public IEnumerable<SelectListItem> AccountTypes { get; set; } rather than List<AccountTypeModel> so that you do not need to generate the SelectList in the view.
To keep it DRY, create a private method to configure your view model
public async Task<IActionResult> Add()
{
var model = new AddAccountViewModel();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public async Task<IActionResult> Add(AddAccountViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
....
}
private async Task ConfigureViewModel(AddAccountViewModel model)
{
var client = await _clientFactory.CreateClientAsync();
var accountTypes = await client.GetAccountTypesAsync();
model.AccountTypes = new SelectList(accountTypes, "Id", "Name");
}

ASP.NET MVC - Proper usage of View Model and Command pattern

I've been writing ASP.NET MVC applications for some time and I found them to be a good place for using the command pattern: we represent every user request as a command - a set of input params - then this command is processed (processing includes validation and other domain logic) and the result is sent back to the user.
Another thing I've been using in my applications is view models. I found them to be a more convenient way of passing data to the view than using domain objects as models or filling ViewData/ViewBag.
These 2 concepts work great for separating data that is shown to the user from user input and its handling, but they don't quite agree with each other in ASP.NET MVC.
Let's say I want to use commands and view models when developing a simple web store where users look through products and can order a product by providing their name and email address:
class ProductViewModel
{
public ProductViewModel(int id) { /* init */ }
public int Id { get; set; }
public string Name { get; set; }
// a LOT of other properties (let's say 50)
}
class OrderProductCommand
{
public int ProductId { get; set; }
[Required(ErrorMessage = "Name not specified")]
public string Name { get; set; }
[Required(ErrorMessage ="E-Mail not specified")]
public string Email { get; set; }
public CommandResult Process() { /* validate, save to DB, send email, etc. */ }
}
When looking through tutorials and SO I've seen people suggest several ways of doing this.
Option 1
Controller:
[HttpGet]
public ActionResult Product(int id)
{
return View(new ProductViewModel(id));
}
[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
if (ModelState.IsValid)
{
var result = command.Process();
if(result.Success)
return View("ThankYou");
else
result.CopyErrorsToModelState(ModelState);
}
return Product(command.Id);
}
View:
#using (Html.BeginForm())
{
#Html.Hidden("ProductId", Model.Id)
#Html.TextBox("Name")
#Html.TextBox("Email")
<input type="submit" value="Place order" />
}
Pros: view model and command are separated from each other, the HttpPost method looks clean
Cons: I can't use convenient HTML helpers like #Html.TextBoxFor(model => model.Email), I can't use client validation (see my other question)
Option 2
We copy Id, Name and Email together with their validation attributes from command to viewModel.
Controller:
[HttpPost]
public ActionResult Product(ProductViewModel viewModel)
{
var command = new OrderProductCommand();
command.Id = viewModel.Id;
command.Name = viewModel.Name;
command.Email = viewModel.Email;
if (ModelState.IsValid)
// ...
}
View:
#Html.TextBoxFor(m => m.Email)
...
Pros: all of option 1 cons go away
Cons: copying of properties seems inconvenient (what if I have 50 of them?), validation of Name and Email in view model (it should be done in command where the rest of the domain logic resides), model as a POST parameter (see below)
Option 3
We make command a property of viewModel.
Controller:
[HttpPost]
public ActionResult Product(ProductViewModel viewModel)
{
var command = viewModel.Command;
if (ModelState.IsValid)
// ...
}
View:
#Html.TextBoxFor(m => m.Command.Email)
...
Pros: all of option 1 cons go away
Cons: view model should only contain data that is displayed to the user (and command is not displayed), model as POST parameter (see below)
--
What I don't like about options 2 and 3 is that we use a view model as a POST method parameter. This method is meant for handling user input (only 2 fields + 1 hidden in this case) and the model contains 50 more properties that I'll never use in this method and that will always be empty. Not to mention the necessity to create an empty constructor for the view model just to handle this POST request and the unnecessary memory consumption when creating large view model objects for every POST request.
My question is (that's like the longest question ever, I know): is there a secret Option 4 for properly using commands and view models that has all of the pros and none of the cons of the other ones? Or am I being paranoid and these cons are not that important and can be ignored?
Seems like the only other decent way go is to use a partial view for rendering the form and use OrderProductCommand as the view model.
Product.cshtml:
#model ProductViewModel
...
#Html.Partial("Product_OrderForm", new OrderProductCommand { ProductId = Model.Id })
...
Product_OrderForm.cshtml:
#model OrderProductCommand
...
#using (Html.BeginForm("Product", "Home"))
{
#Html.HiddenFor(cmd => cmd.ProductId)
#Html.TextBoxFor(cmd => cmd.Name)
#Html.TextBoxFor(cmd => cmd.Email)
<input type="submit" value="Place order" />
}
...
This way there is no need to create a data map between view models and business objects, and the controller code can be left clean as it was in in Option 1:
[HttpGet]
public ActionResult Product(int id)
{
return View(new ProductViewModel(id));
}
[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
// process command...
}
Personally,
If I had to pass my model back to the view using a viewModel, I'd use option 4, inherit my view model from my command.
That way I get all the properties for my command, and I can set new properties that are just needed for the view, say dropdown list options etc.
Let inheritance do the work for you.
Also, you don't need to copy properties, in your post, don't send back a ViewModel, send back the command.
public ActionResult Product(PreOrderProductCommand command)
Don't forget, Mvc doesn't care what model is in your view, it only maps keys on the formcollection to properties in the model in the argument list. So even though you send a ProductViewModel out, you can still get a PreOrderProductCommand in.
HTH
Here's my take on the issue.
The reason we introduce all these layers (entity, view model, command etc.), which essentially represent the same concept within different domains, is to enforce separation of concerns.
However, as each layer is introduced, we increase the complexity and margin for error, due to increased mapping between objects and distributed validation.
In my opinion it is absolutely correct that entities and view models are implemented separately; a domain entity should represent the business logic, and shouldn't be polluted with UI specific features. Similarly, a view model shouldn't contain more than is necessary than to satisfy a specific view.
There is no reason, on the other hand, that commands should introduce a new layer to your architecture. A command simply needs to provide data, doesn't need to rely on a specific implementation, and therefore can be defined as an interface:
interface IOrderProductCommand
{
int ProductId { get; }
string Name { get; }
string Email { get; }
}
Whilst a view model isn't a command, or vice-versa, a view model could act as a command:
class ProductViewModel : IOrderProductCommand
{
public int ProductId { get; set; }
[Required(ErrorMessage = "Name not specified")]
public string Name { get; set; }
[Required(ErrorMessage ="E-Mail not specified")]
public string Email { get; set; }
public ProductViewModel(int id) { /* init */ }
// a LOT of other properties (let's say 50)
}
This way, the validation takes place only in the view model, which I think is the correct place for that to happen, as feedback can be given to the user immediately.
The command should simply transfer data, and the domain entity it mutates should validate itself anyway; a third layer of validation is unnecessary.
Your controller would then look as follows:
readonly CommandHandler _handler;
public YourController(CommandHandler handler)
{
_handler = handler;
}
[HttpGet]
public ActionResult Product(int id)
{
return View(new ProductViewModel(id));
}
[HttpPost]
public ActionResult Product(ProductViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
_handler.HandleProductCommand(model);
return RedirectToAction(nameof(Product), new { id = model.ProductId });
}
And the handler:
class CommandHandler
{
void HandleProductCommand(IOrderProductCommand command)
{
// Update domain...
}
// Other command handling methods...
}

How to show ModelState validation messages to the user on the initial GET

Ok, So the people I'm working for want the form validation messages to show immediately on the initial load. In my ViewModel I have all my requireds etc... which usually get hit on the POST, So how do I force the validation to take place on the GET so the user see's these validation messages when they first enter the form?
I've tried
ValidateModel(viewModel);
and
TryValidateModel(viewModel)
But these aren't working. So is there an easy way?
So I have something like this:
[HttpGet]
public ActionResult Index()
{
var viewModel = new RandomVieModel();
ValidateModel(viewModel); //For example
return View(viewModel);
}
[HttpPost]
public ActionResult Index(RandomViewModel viewModel)
{
if(!ModelState.IsValid)
{
return View(viewModel);
}
return RedirectToAction("SomethingNew);
}
This is an example of my View Model:
public class RandomViewModel
{
[MaxLength(100)]
[MinLength(3)]
public string SomeRandomText { get; set; }
[Required]
public string RandomName { get; set; }
}
What will the validation messages have on an initial GET? Since nothing has "failed" yet (as those are validated on a POST), there will be no validation messages? (The user hasn't provided any input?)
If you are using your validation messages to guide the user on how they should be providing values ("Please enter an alpha numeric postal code"), you really should be not using validation messages for those. That's text that should always be there. If you are looking to avoid duplication of the same text message, then use resource files.
If this is a temporary thing (and you know that already), then anything you do is going to be a hack. Either add it to your HTML or change your controller so that it always checks for the validity of the model:
public ActionResult Index(RandomViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
if (Request.HttpMethod == HttpMethod.Post)
{
return RedirectToAction("SomethingNew");
}
else
{
return View(viewModel);
}
}
The above is going to be your action method for GET and POST.
Ok, So I found a temporary solution. Luckily its not forever.
So I found that jquery validate is an ok option to use. It will trigger the validation on the client side.
<script type="text/javascript">
$(document).ready(function () {
$('#form-id').validate().form();
});
</script>

Rendering LabelFor doesn't display the overwritten getter function

pardon me if this is a noob question but I have already searched on SO, google, and spend over an hour with an ASP.NET MVC 4 PRO book reading on Rendering Stronly Typed Helpers etc. I am just beginning learning ASP.NET 4 MVC so please go easy on me.
I have a model class called USER and a property called Name with an overwritten getter and setter (its me calling it this way, im not sure if this is the proper naming in the case)
//using...
namespace MvcMyApplication1.Models
{
public class User
{
[ScaffoldColumn(false)]
public int Id { get; set; }
public string Name
{
get
{
return WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
set
{
Name = WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
}
[Required(ErrorMessage="Password is required")]
public string Password { get; set; }
}
}
So, In my View I am trying to display the result of the get function, but I am lost and not sure how to do this.
#using(Html.BeginForm())
{
#Html.ValidationSummary(excludePropertyErrors: true);
// changed from LabelFor to DisplayFor
#Html.DisplayFor(m => m.Name)<br />
#Html.PasswordFor(m => m.Password)<br />
<input type="submit" value="Log in" />
}
I have tried to add attributes but then I am not sure how to assign the Name= to the get function
[DisplayName(Name="I want to call the get function here")]
public string Name { get; set; }
In my controller I have this code:
[HttpGet]
public ActionResult Index()
{
User newUser = new User();
return View();
}
[HttpPost]
public ActionResult Index(User m)
{
if (ModelState.IsValid)
{
return View("Report", m);
}
{
return View(m);
}
}
And this goes to the Report View
which normally displays the Windows login
#using (Html.BeginForm())
{
#Html.DisplayForModel()
}
EDIT: After swapping the LabelFor with DisplayFor the windows login is rendered but only after clicking the log in button. It does not render the first time I open the page
You're not passing in the model to the initial view:
return View();
should be
return View(newUser);
You should be using DisplayFor not LabelFor if you want to retrieve the property value.
#Html.DisplayFor(m => m.Name)
To get current authenticated user in the view, you can use #User.Identity.Name.
In controller, use User.Identity.Name
To display "I want to call the get function here" in DisplayName attribute in the view, use #Html.DisplayNameFor(m => model.Name)
Use WindowsIdentity in your class is not a good idea. For MVC, better use User.Identity.Name (use this in controller), the reason is this way it is get the user name under current HttpRequest, use WindowsIdentity might be fine, but careful. Depending on how you use it, it might return error or return the service account which ran the application pool.
Normally, getter and setter are written like this by calling a private property:
public class User{
private string _name;
public string Name
{
get{
return _name;
}
set{
_name = WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
}
It looks like your MVC app is using Windows Authentication, which is an intranet app, so your user will be automatically authenticated, which means logged on already.

Supplying a ID to many partial views from a parent view without exposing sensitive ID's

We are using a friendly Name URL route scheme. Basically using a combination of the Principal Identity and a friendly name this can be mapped back to an identity internally (Person ID). So a route like this:
routes.MapRoute(string.Empty, "{friendlyname}/Products/{action}", new {controller = "Products", action = "List"});
Would map to a URL like this:
Adam/Products/List
This all works fine and abstracts away the internal Id of the named person which is required as well.
The problem is our Views are comprised of many partial views. When there are rendered by using the #Html.Action method they ultimately need the PersonID but from the URL we only have the 'friendly name'.
I have thought about this for a while and there are two solutions to my mind:
Pass the 'friendly name' into each of the controller action methods that return the partial views and internally the method will have to do a lookup on the currently logged in identity and the friendly name. This will give the PersonID to me and I can then efficiently query from then on. The only problem with this apporach is that due to the multiple partial views I will be querying on the currently logged in identity and friendly name for each partial view call which is innefficeint and I feel I should only have to write this code once.
Somehow query in the view and get the PersonID so it can be passed to each #Html.Action call so the partial view controller methods will not have to do that lookup themselves saving round trips to the database for the same shared informtion. The problem with this is that I am not sure of a way of doing this cleanley in the view using the DI that we use through the rest of the application.
Any thoughts on approach to would be greatly appreciated.
Thank you,
Adam
You could add the Id to the session variables and access it from within the views with:
#{var personId = int.Parse(Session["PersonId"])}
Then you can pass it directly to partial views from the Parent without it hitting the client or having to pass parameters to any controllers.
Update
You could also access the session variable from the Controller if you wanted to do the work there instead without roundtripping to the database.
EDIT
If you put the property in a model and pass it to a page that post's back then the model will not persist between posts.
If for example your controller does:
[HttpPost]
public ActionResult DoSomething(ViewModel model)
{
if(ModelState.IsValid)
{
// Logic Here
}
return View(model)
}
when the page is reloaded, the model will have forgotten about the ID.
There are a couple of ways around this. Either use #Html.HiddenFor(m => m.ID)
which will put the property in the rendered HTML, which if it is truely a sensitive piece of information, is bad.
Or you can rebuild the view model on each subsequent postback.
Hope this helps
As Marc states I could you the Session to deal with this but I have gone with using the Model as he has stated in his update. If the parent View Controller action takes in the friendly name it can do the lookup, put the PersonID into the model and then any Partial Renders can have the models value passed into them in the parent View Controllers Action's view. An example is shown below (this is demo code but it hopefully gets the point across, I would never use a static data context in real code)
Home controller
public class HomeController : Controller
{
public ActionResult Index(string friendlyName)
{
int pupilId = Data.People.Single(x => x.Name == friendlyName).PersonId;
HomeIndexViewModel homeIndexViewModel = new HomeIndexViewModel {PupilId = pupilId};
return View(homeIndexViewModel);
}
}
Home Index View
#model SharingInformationBetweenPartials.Web.Models.HomeIndexViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.Action("DisplayPersonDetail", "Person", new {Model.PersonId})
The PersonController's DisplayPersonDetail method can then present the respective data it wishes using the passed in PersonId:
public class PupilController : Controller
{
[ChildActionOnly]
public ActionResult DisplayPupilDetail(int pupilId)
{
Person person = Data.People.Single(x => x.PersonId == pupilId);
return View(person);
}
}
I did try what I thought was this before but I must have got something wrong as the ViewModels properties were getting shown in the URL which is what I was trying to get away from. Anyway, I hope this helps anyone else who may be looking to do something similar. If you have any questions then let me know.
Thanks,
Adam
You can use [Bind] attribute to specify the exact properties a model binder should include in binding or use Exclude parameter on the attribute to exclude PersonId
[HttpPost]
public ActionResult EditPerson([Bind(Exclude = "PersonId")] Person person)
{
//do domething
}
You can also use [ReadOnly] attribute that model binder will understand and not assign to that property.
[ReadOnly(true)]
public int PersonId{ get; set; }
But the best approach is to use separate ViewModels: one only for viewing and one for editing.
public abstract class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
public class PersonCreateVM : Person
{
//no PersonId here
}
public class PersonEditVM : Person
{
public int PersonId{ get; set; }
}
This approach is maybe a "overkill" but when used properly and with AutoMapper http://automapper.codeplex.com/ it's an ease to work with.

Categories

Resources