ASP.NET MVC 2 RC model binding with NHibernate and dropdown lists - nhibernate

I have problem with model binding in my ASP.NET MVC 2 RC application that uses NHibernate for data access. We are trying to build the application in a Ruby on Rails way and have a very simple architecture where the domain entities are used all the way from the database to the view.
The application has a couple of domain entities which can be illustrated by the following two classes:
public class Product {
...
public Category Category { get; set; }
}
public class Category {
public int Id { get; set; }
public string Name { get; set; }
}
In the view that renders the edit form has the following statement to display a dropdown list:
<%= Html.DropDownListFor(model => model.Category.Id,
new SelectList(ViewData["categories"] as IList<Category>, "Id", "Name"),
"-- Select Category --" ) %>
Please disregard the use of "non-typed" view data to hold the category collection.
The action method that receives the form post is similar to to the following. Note that the TransactionFilter attribute adds NHibernate transaction handling and commits the transaction if no exceptions occur and validation succeeds.
[HttpPost]
[TransactionFilter]
public ActionResult Edit(int id, FormCollection collection) {
var product = _repository.Load(id);
// Update the product except the Id
UpdateModel(product, null, null, new[] {"Id"}, collection);
if (ModelState.IsValid) {
return RedirectToAction("Details", new {id});
}
return View(product);
}
My issue is that the product.Category.Id is set with the value selected in the form, e.g. Category.Id = "2". Using the default model binder results in the following type of NHibernate exception:
identifier of an instance of Name.Space.Entities.Category was altered from 4 to 2
That makes a lot of sense since the product already has a category assigned and only the primary key of that existing category is being changed. Another category instance should have been assigned instead.
I guess a custom ModelBinder can be created to handle this issue but is there a simpler way to make this work? Can (and should) the domain entities be modified to handle this?

I solved a similar problem with combo boxes on my edit page by changing the following line
#Html.DropDownListFor(x => x.Employee.Id, new SelectList(ViewBag.Employees, "Id", "DisplayName"))
by
#Html.DropDownListFor(x => x.Employee, new SelectList(ViewBag.Employees, "Id", "DisplayName"))
So I removed the '.Id' like Bryan suggested. Before the change, the model only contained the Id of the Employee and after the change, the Binder also loaded all the details of the employee into the model.

I've used similar techniques with Linq to SQL classes before with no problems. I don't think you'd need a custom ModelBinder for this. UpdateModel should be updating the Product class you are passing into it - not the Category sub-class attached to it. Check the html generated by the DropDownListFor helper. What is the name of the element? It should be the name of the foreign-key field in your Products table (e.g. "CategoryID" or "Product.CategoryID" not "Category.Id"). If it's "Category.Id" - try changing the first parameter of the DropDownListFor to either "model => model.Category" or "model => model.CategoryID" (or whatever the foreign key field is). This should cause UpdateModel to only update the foreign-key field in the Product class and not the Category class ID.

The solution we chose at the time was something similar to this:
TryUpdateModel(product, null, null, new[] {"Category"}, collection);
int categoryId;
if (int.TryParse(collection["Category.Id"], NumberStyles.Integer, CultureInfo.InvariantCulture, out categoryId) && categoryId > 0) {
product.Category = _categoryRepository.Load(categoryId);
}
else {
product.Category = null;
}
We simply tell the model binder to exclude the association property and handle that manually. Not pretty but worked at the time....

Related

Instantiating ModelExpression directly

Let's say I have the following input tag which utilizes the built-in tag helper:
#model ProductViewModel
<label asp-for="Product.Id"></label>
In my case, this expands into the following:
<label for="Product_Id">Id</label>
I see that asp-for is expecting a ModelExpression:
In tag helper implementations, I often see a property like the following:
public ModelExpression For { get; set; }
It appears that this is automatically populated when the tag helper is used.
Is there a way to instantiate a ModelExpression directly in C#?
I.e. something like this:
var exp = new ModelExpression("Product.Id",...)
I'd like to be able to generate "Product_Id" and "Id" from Product.Id as the input tag helper did.
As far as I know, you can specify that your property is to be set to the name of some property on the View's Model object by declaring your property with the ModelExpression type. This will enable any developer using your property to get IntelliSense support for entering a property name from the Model object. More importantly, your code will be passed the value of that property through the ModelExpression's Model property.
Sample code as below:
[HtmlTargetElement("employee-details")]
public class EmployeeDetailTagHelper : TagHelper
{
[HtmlAttributeName("for-name")]
public ModelExpression EmployeeName { get; set; }
[HtmlAttributeName("for-designation")]
public ModelExpression Designation { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "EmployeeDetails";
output.TagMode = TagMode.StartTagAndEndTag;
var sb = new StringBuilder();
sb.AppendFormat("<span>Name: {0}</span> <br/>", this.EmployeeName.Model);
sb.AppendFormat("<span>Designation: {0}</span>", this.Designation.Model);
output.PreContent.SetHtmlContent(sb.ToString());
}
}
Code in the View page:
#model WebApplication7.Models.EmployeeViewModel
<div class="row">
<employee-details for-name="Name" for-designation="Designation"></employee-details>
</div>
Code in the Model
public class EmployeeViewModel
{
public string Name { get; set; }
public string Designation { get; set; }
}
From above code, you can see that we could custom the attribute name. More detail information about using the ModelExpression, check the following links:
Creating Custom Tag Helpers With ASP.NET Core MVC
Expression names
I'd like to be able to generate "Product_Id" and "Id" from Product.Id
as the input tag helper did.
Besides, do you mean you want to change the Product. Id to Product_Id, in my opinion, I'm not suggesting you change it, because generally we can use "_" as a separator in the property name. So, if we are using Product.Id, it means the Product's Id property, and the Product_Id means there have a Product_Id property.
To answer the question:
Is there a way to instantiate a ModelExpression directly in C#"
Yes you can, through IModelExpressionProvider and its CreateModelExpression method. You can get an instance of this interface through DI.
Now, if you're already in your view and working with tag helpers, Zhi Lv's answer is all you need, as the functionality is built-in and much easier to use. You only need IModelExpressionProvider for when you're in your Razor Page, Controller, or perhaps some custom middleware. Personally, I find this functionality useful for my Ajax handlers that need to return one of my ViewComponents that has a ModelExpression argument (so that I can easily call it from my Pages/Views too.)
To call CreateModelExpression, you'll need a strongly-typed instance of ViewData. In Razor Pages, this is as easy as casting the ViewData property to the strongly-typed instance of your PageModel's type (presuming you don't have a page model hierarchy):
var viewData = (ViewDataDictionary<IndexModel>)ViewData;
If you're using MVC and you're in the controller, that won't exist yet. Best you can do is make your own instance.
var viewData = new ViewDataDictionary<ErrorViewModel>(new EmptyModelMetadataProvider(),
new ModelStateDictionary());
Once you get your strongly-typed ViewData instance, you can obtain your desired ModelExpression like this, just like using a lambda expression in your views:
var myPropertyEx = _modelExpressionProvider.CreateModelExpression(viewData,
m => m.MyProperty);

Passing enum value as parameter .net core

I have a controller (ResponseResourceController) which Index Action is supposed to receive as parameter int and in the Index action - Parse enum value from Tempdata. Then this action redirects to another action with this two parameters.
public class ResponseResourceController : Controller
{
public async Task<IActionResult> Index(
int id)
{
var entityType = (EntityType)TempData["EntityType"];
var exists = await _responseResourceStringUiService.ResponseResourceStringExistsAsync(
id,
entityType);
return RedirectToRoute(!exists ?
RouteNames.ResponseResourceString_Home_Add :
RouteNames.ResponseResourceString_Home_Edit,
new {id, entityType});
}....
}
public class PublicationController : Controller
{
public async Task<IActionResult> Index()
{
var vm = new PublicationViewModel
{
...
};
TempData["EntityType"] = InboxEntityType.Publication;
........
}
My question is - is there more elegant way to achieve this without the enum value beeing in the url after the redirection.
I was thinking for example to create an abstract property of thids Enum Type in this ResponseResourceController
AND the other controller PublicationController(and othe that are going to do that) to inherit it and override the property with the right enum value and as the ResponseResourceController will have property I will not need the entity type as parameter in the Add and Edit Action.
Why don't just directly call the PublicationController and avoid creating an unnecessary redirect? You can distinguish directly in the frontend if it is a new or an existing record.
If you wanna use this method for creating new records then you should prefer to use a POST request instead of GET.
In short, multiple controllers does not work and url redirection is not necessary.
From the description I think what you really want is displaying the add/edit form base on an ID (with your temp entity type stored in somewhere). If this is what you want then single controller with multiple views should be able to handle that.
What will happen when you create multiple controllers?
The routing rules in the app has to be unique. If you have a ResponseResourceController and a PublicationController and each of them has the Index action, what will be the routes look like?
You probably need to define a route like /ResponseResource/Index for ResponseResourceController.Index() and a route like /Publication/Index for PublicationController.Index(). If there is a EntityType called Bob, you may need to define a route like /Bob/Index and create a BobController.
However, if you try to name a unique route for each of the EntityType, your route has to contain that piece of information (either in the route or in the query string). So, creating a dedicated controller for each EntityType cannot solve your problem.
Multiple views for multiple entity types
If you don't want to include the EntityType in the url, you may use ResponseResourceController.Index(int id) to get the entity ID and respond with different views for different EntityType:
[Route("/response-resource")]
public class ResponseResourceController : Controller
{
[HttpGet("")]
public async Task<IActionResult> Index(int id)
{
var entityType = (EntityType)TempData["EntityType"];
var exists = await _responseResourceStringUiService.ResponseResourceStringExistsAsync(id, entityType);
var viewName = exists ? "_AddEntity" : "_EditEntity";
var vm = new GenericViewModel
{
type = entityType,
/* other fields */
}
return View(viewName, vm); // so user may see a different view under different situation
}
}
And for the view model:
public class GenericViewModel
{
public EntityType type { get; set; }
}
You also need to define 2 views: _AddEntity and _EditEntity. In the above example, GenericViewModel contains the EntityType so you will know which entity you are handling.
Furthermore, you can even define a dedicated view for each EntityType and pass a dedicated view model to that view. e.g. PublicationViewModel will be the view model used in _AddPublication and _EditPublication views.
Expected behavior
When user requests for ResponseResource with and id, e.g. /response-resource?id=123, your application will render the view base on the id and the EntityType. No url redirection and url won't change.

Same view for both create and edit in MVC4

Can we have a single razor view for both Create and Edit operations?
If yes, how do we achieve this?
I don't recommend it.
This should be a rather long answer, because there's a lot of things involved in the process, request and workflow of a normal MVC GET/POST workflow. I will try to answer your question with the minimum information required and why I do not recommend the use of the same view.
First, why?
You don't have control over the views, which may have over-posting;
No flexibility;
Not reusable views or parts;
Hard to maintain the views (one change in the view must be tested on both actions).
My suggested approach would be to have different actions/views but share common code:
Create both views as normal.
You will have duplicated code, but not all code is the same, for example, you may not want to send an ID on the create action, this is not directly related to your question, but using the same view implies you are also sending the same data, and this is not recommended, especially for over-posting or mass assignment. More info about mass assignment here (an Architectural Approach is what I'm using here).
So let's start from what are you going to receive in your controllers.
In this case I used inheritance but it's not the only strategy.
Binding models
public class UpdateBindingModel : CreateBindingModel {
// since we are not using the same binding model,
// we can have a "real" validation rules on our update binding and view.
[Required]
public int? Id {get;set;}
}
public class CreateBindingModel {
// no id here prevent overposting.
[Required]
public string Name {get;set;}
[Required]
public int? CountryId {get;set;}
}
That will make sure the data you send to your Create and Edit is the minimum needed and nothing else.
Let's then see the View Models that will be sent to the View, for this example I will include a List that will be used to select some value but should not be posted (the list) to the controller, only the selected value.
View models
public class CreateViewModel : CreateBindingModel {
public IEnumerable<SelectListItem> CountryList {get;set;}
}
public class UpdateViewModel : UpdateBindingModel {
public IEnumerable<SelectListItem> CountryList {get;set;}
}
As you can see, this gives you lot of flexibility but still have some duplicated code (the extra information needed on view model for both views) which can be mitigated in several ways (depending the needs/context):
Have an action to retrieve the common data and using #Html.Action("GetCountryList");
Use the same View Model aka CreateUpdateViewModel and discarding extra UpdateBindingModel properties in the view but still posting the corresponding model on POST.
Having your binding models as properties and select one or the other in the specific view. (better use #Html.EditorFor instead of partials so Model Binder will work with no additional change on code)
The controller actions will look like:
Controller
[HttpGet]
public ActionResult Create(){
ViewData.Model = new CreateViewModel();
return View();
}
[HttpPost]
public RedirectToRouteResult Create(CreateBindingModel binding) {
// check valid model state and create data
return RedirectToAction("Index");
}
[HttpGet]
public ActionResult Update(int id) {
var objectToEdit = service.GetObjectToEdit(id);
ViewData.Model = new UpdateViewModel(objectToEdit);
return View();
}
[HttpPost]
public RedirectToRouteResult Update(UpdateBindingModel binding) {
// check valid model state and update data
return RedirectToAction("Index");
}
And your views:
Views
Update.cshtml
<form action="Update">
#Html.HiddenFor(Model.Id);
#Html.Partial("EditFieldsPartial")
<button>delete</button> // no delete button on create.
<button>create new</button> // you can have a create new instead of update.
</form>
Create.cshtml
<form action="Create">
#Html.Partial("EditFieldsPartial")
</form>
Note: code is incomplete and didn't use helpers in most cases for brevity and clarity. Do NOT copy paste :D
Sure you can.
On post, check in your controller whether the primary key has value 0 then Insert, otherwise Update.
View should be the same for Create and Edit.
Just remember to include:
#Html.HiddenFor(model=>model.ID)
In your view
For example:
Model:
public class DescriptionModel
{
[Key]
public int ID { get; set; }
public string Description { get; set; }
}
CreateEdit.cshtml:
#model DescriptionModel
#using (Html.BeginForm("CreateEdit"))
{
#Html.HiddenFor(model=> model.ID)
#Html.EditorFor(model=> model.Description)
<input type="submit" value='Submit' />
}
DescriptionModel controller:
public ActionResult Create()
{
return View("CreateEdit", new DescriptionModel());
}
public ActionResult Edit(int id)
{
return View("CreateEdit", db.DescriptionModels.Find(id));
}
// Submit and add or update database
[HttpPost]
public ActionResult CreateEdit(DescriptionModel model)
{
if (ModelState.IsValid)
{
// No id so we add it to database
if (model.ID <= 0)
{
db.DescriptionModels.Add(model);
}
// Has Id, therefore it's in database so we update
else
{
db.Entry(model).State = EntityState.Modified;
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
A View can definitely be shared for create and edit operations, using the same model. However, i would strongly recommend to think about it twice. In many cases, you will want to have a different view for edit operations(eg. hide some inputs that should not be editible) as well as the model could be slightly different, altought it might share some (or most) values. These difference will lead to some conditions in the view, checking whether you are creating or editing - which could make the code chaotic.
Conclusion: before deciding whether to have a shared view, try to think of how much is the edit screen gonna differ from create screen, then you may decide.
You certainly can, but usually that's something I will try to avoid. If the create and edit actions are virtually the same then you end up duplicating a lot of code in the controller. Usually in this situation I will have only a few fields on my 'Add' controller, and then once the item has been added I redirect the user to the edit page where they can fill in the rest of the information.
I wouldn't recommend that approach but you could have the main form be loaded into both views from a partial
[HttpGet]
public ActionResult myFun(int id = 0)
{
MyClass cls = new MyClass();
if (id == 0)
{
//Insert mode ... no data will be shown to textboxes , when primary key ie. id=0
//Display whole data
}
else
{
//Update mode... if id is not 0 ,data will be shown to textboxes
}
return View(cls);
}

How to hide a Model class field based on custom logic in MVC Web Api RC

I am using Asp.Net Mvc Web api RC.
I wanted to hide the fields/properties of my model class using custom attribute. Below is my class:
public class Employee
{
public int EmpId { get; set; }
public string Name{ get; set; }
//Wanted to hide this attribute based on custom logic. Like for a certain role, i want to hide the designation
public string Designation{ get; set; }
public string Department{ get; set; }
}
How can we achieve using Data Annotations. I mean i wanted to create a separate attribute to use in this manner:
[HideForRoles(Roles="Admin,Writer")]
public string Designation{ get; set; }
UPDATE :
As i am developing web api. The response is serialized to either XML or Json format depend upon the formatter. So better question would be how not to allow the fields to be serialize while writing to the response.
However one option could be using IgnoreDataMember attribute. Like
[IgnoreDataMember]
public string Designation{ get; set; }
But the above is a compile time declaration where i cannot impose any condition.
Question: How to ignore the field/property while serializing based on some condition at runtime?
Totally missed on the first go-round that you were using Web Api, my apologies.
What you want to do is to create a custom formatter.
There's a good article here on the flow/differences between MVC and Web Api (which I'm getting that you already understand, still some valid points here):
http://lostechies.com/jimmybogard/2012/04/10/asp-net-web-api-mvc-viewmodels-and-formatters/
And here's a sample implementation of a custom formatter:
http://www.tugberkugurlu.com/archive/creating-custom-csvmediatypeformatter-in-asp-net-web-api-for-comma-separated-values-csv-format
Building from that, you would use reflection to read from the attributes, building on the custom ActionFilterAttribute you would have to write, where you evaluate the user's roles and determine which fields should be omitted/included. Here's a sample of an action filter:
https://github.com/MisterJames/MovieFu/blob/master/MovieFu/ActionFilters/UserNameFilter.cs
Hope this helps more.
Cheers.
Your best bet is to return a dynamic object. In this case you can say:
dynamic viewModel = new ExpandoObject();
viewModel.Id = 12;
if(role == "Admin")
{
viewModel.SecureStuff = "Others should not see it";
}
It won't be as straightforward as that, as you'll need to have the fields conditionally rendering in the view. But you can get most of the way there through the attribute.
You will need to make your custom attribute meta-data aware, then check the attribute in your view. A solution is posted here: Can't get Custom Attribute Value in MVC3 HTML Helper.
Cheers.
I have done the authorization checking in the model repository itself. Rather ideal way was to create custom formatters for hiding the certain fields based on some condition.
After getting the list of Employees from db and have them in list, i iterated again and place a NULL to the fields i don't want to display.
The code i have written as:
foreach (var employee in listEmployees)
{
//get all props. of Employees object using reflection
var props = employee .GetType().GetProperties();
//loop through each field to match with the field name to remove/place null
foreach (var propertyInfo in props)
{
var fieldName = propertyInfo.Name;
if (fieldsNamesToRemove .Contains(fieldName))
{
propertyInfo.SetValue(employee , null, null);
}
}
}
here fieldsNamesToRemove is a list that i created dynamically based on roles of current user.
This solution actually placing a NULL for the fields we do not want display. As a result in JSon format the fields are not displaying but in the XML the fields are displaying with syntax like lt; Designation i:nil="true"/ gt;, but manageable as we need to deal mostly with json response.
Thanks Ali and MisterJames for your valuable suggestions

Is there something analogous on NHibernate regarding Entity Framework's navigation property?

Is there something analogous on NHibernate regarding Entity Framework's navigation property? For example, instead of:
s.Save(new Product { Category = s.Get<Category>("FD"), Name = "Pizza" });
I wish I could write:
s.Save(new Product { CategoryId = "FD", Name = "Pizza" });
Can I inform NHibernate not to use the Product's Category property as a mechanism to save the Product's category? I want to use CategoryId instead(Read: I don't want to use DTO). Entity Framework seems able to facilitate avoiding DTO patterns altogether, while at the same time offering the full benefit of ORM(can avoid joins using navigation properties). I want the EF's offering the best of both worlds(lean mechanism for saving objects, i.e. no need to retrieve the property's object) and navigation mechanism for querying stuff
Sample from EF: http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-code-first-walkthrough.aspx
public class Category
{
public virtual string CategoryId { get; set; }
public virtual string Name { get; set; }
public virtual IList<Product> Products { get; set; }
}
public class Product
{
public virtual int ProductId { get; set; }
public virtual string Name { get; set; }
public virtual string CategoryId { get; set; }
public virtual Category Category { get; set; }
}
[UPDATE]
Regarding James answer, I tried seeing the NHibernate's actions in SQL Server Profiler.
// this act didn't hit the Category table from the database
var c = s.Load<Category>("FD");
// neither this hit the Category table from the database
var px = new Product { Category = c, Name = "Pizza" };
// this too, neither hit the Category table from the database
s.Save(px);
Only when you actually access the Category object that NHibernate will hit the database
Console.WriteLine("{0} {1}", c.CategoryId, c.Name);
If I understand your question, you want to save a Product with a Category without hitting the database to load the Category object. NHibernate absolutely supports this and you almost have the right code. Here is how you do it in NHibernate:
s.Save(new Product { Category = s.Load<Category>("FD"), Name = "Pizza" });
This will not hit the database to fetch the actual Category, but it will simply save a Product with the correct Category.Id. Note that you don't need (and I would recommend getting rid of Product.CategoryId).
Now why does this work with session.Load(), but not session.Get()... With session.Get(), NHibernate has to return the object or null. In .NET, there is no way for an object to replace itself with null after the fact. So NHibernate is forced to go to the database (or L1 cache) to verify that the "FD" Category actually exists. If it exists, it returns an object. If not, it must return null.
Let's look at session.Load(). If the object is not present in the database, it throws an exception. So NHibernate can return a proxy object from session.Load() and delay actually hitting the database. When you actually access the object, NHibernate will check the database and can throw an exception at that point if the object doesn't exist. In this case, we're saving a Product to the database. All NHibernate needs is the Category's PK, which it has in the proxy. So it doesn't have to query the database for the Category object. NHibernate never actually needs to hydrate an actual Category object to satisfy the save request.