ASP.NET MVC Bad Practices: Optional Submodel With Required Property - c#

There are loads of resources for this on Google but I can't fully understand what I need to do in my scenario:
I have this class:
public class CompanyLanguage : EntityBase
{
public int CompanyId { get; set; }
public int LanguageId { get; set; }
public bool IsDefault { get; set; }
public virtual Company Company { get; set; }
public virtual Language Language { get; set; }
}
Language is defined as:
public class Language:EntityBase
{
[Required]
[DisplayName("Language Code")]
public string LanguageCode { get; set; }
[Required]
[MaxLength(2, ErrorMessage ="2 characters maximum")]
[DisplayName("2 Char Language Code")]
public string LanguageCode2Char { get; set; }
[Required]
[DisplayName("Language Name")]
public string LanguageName { get; set; }
public virtual List<LabelLanguage> LabelLanguages { get; set; }
}
Running a Fortify Scan returns the issue below as a high priority:
(ASP.NET MVC Bad Practices: Optional Submodel With Required Property)
We can't run the fortify scan - it's being run by someone else, so I need to get the changes right so it doesn't come straight back.
All the resources I've looked at suggest that underposting attacks could be made - i.e. a null Language, even though Language has some required properties.
For me, this is a valid scenario - the required properties of Language are only required if Language isn't null.
So what am I supposed to do to resolve this? Do I make public int LanguageId { get; set; } required, or public virtual Language Language { get; set; } or both?
Or am I completely wrong an I have to do something else? As I say I can't test these as the software has to be sent away for the test or I'd be trying all sorts out.

To summarize our discussion from comments.
Create a view model which models only the information that is needed to satisfy the corresponding view.
populate view models in your controller action from your domain ef models
either project directly into view models using linq queries or Automapper.
Example view model for your question
public class CompanyLanguageEditViewModel
{
[DisplayName("Company")]
[Required]
public int CompanyId { get; set; }
[DisplayName("Language")]
[Required]
public int LanguageId { get; set; }
public bool IsDefault { get; set; }
public IEnumerable<SelectListItem> Companies{ get; set; }
public IEnumerable<SelectListItem> Languages { get; set; }
}
And in your view you can then use
#Html.DropDownListFor(x => x.CompanyId, Model.Companies);
and your label will be Country and you are only going to POST back what you need

Related

How to validate Business Entity in Razor view instead of Data Entity

I am working on an ASP.NET application split in to 4 layers.
Business Layer
Data Access Layer
WCF Services Client
ASP.NET MVC5 application
I am using AutoMapper to map from domain class to business class and other way around. I am using Data Annotation:
[StringLength(250)]
[Required(ErrorMessage = "Required Title")]
[Display(Name = "Function Title")]
Which is translated in view ASP.NET Razor page, i.e. creating record by passing strongly typed and validate against that. My question is how I can achieve this where I am passing business class to view as strongly typed and I don't want data dependency on business layer nor ASP.Net MVC application?
DAL Entity
[Table("Navigation_Functions")]
public class Navigation_FunctionsEntity
{
public Navigation_FunctionsEntity()
{
}
[Key]
public int Function_ID { get; set; }
[StringLength(250)]
[Required(ErrorMessage = "Required Title")]
[Display(Name = "Function Title")]
public string FunctionName { get; set; }
[Required(ErrorMessage = "Required Hierarchy Level")]
[Display(Name = "Hierarchy Level")]
public int Hierarchy_Level { get; set; }
public ICollection<Navigation_FunctionHierarchy> Navigation_FunctionHierarchy { get; set; }
public ICollection<Navigation_FunctionInAction> Navigation_FunctionInAction { get; set; }
public ICollection<Navigation_FunctionInController> Navigation_FunctionInController { get; set; }
public ICollection<Navigation_FunctionController> Navigation_FunctionController { get; set; }
}
Business Entity
public class Navigation_Functions
{
public int Function_ID { get; set; }
public string FunctionName { get; set; }
public int Hierarchy_Level { get; set; }
public ICollection<Navigation_FunctionHierarchy> Navigation_FunctionHierarchy { get; set; }
public ICollection<Navigation_FunctionInAction> Navigation_FunctionInAction { get; set; }
public ICollection<Navigation_FunctionInController> Navigation_FunctionInController { get; set; }
public ICollection<Navigation_FunctionController> Navigation_FunctionController { get; set; }
}
My advice is don't organize your code into layers like this. It's a lot of hoops but there's no real advantage. In fact, I found over time that a layered architecture like this actually hinders refactoring and evolution of your codebase. Layers are for cake, not software.
https://vimeo.com/131633177
I also question the value of WCF in your picture too, I don't know what it adds to the solution here except adding latency and making your requests slower.

The entity type 'Microsoft.AspNet.Mvc.Rendering.SelectListGroup' requires a key to be defined

Not sure what happened but I am getting the following error while attempting to pull up any view in my web app. The code is auto generated by visual studio and I am not getting any errors before building. Using ASP.Net MVC 6, EF7.
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.Core.dll but was not handled in user code
Additional information: The entity type 'Microsoft.AspNet.Mvc.Rendering.SelectListGroup' requires a key to be defined.
Here is the line the code is erroring out on.
public IActionResult Index()
{
var schoolContext = _context.Schools
.Include(s => s.District)
.Include(s => s.Location)
.Include(s => s.Tier);
return View(schoolContext.ToList());
}
After some searching I can't figure out exactly what I need to fix. This was working at one point. Not sure what changed.
The view does have a defenition
#model IEnumerable<School>
As requested here is the School model
public class School
{
//Original Fields
public int SchoolId { get; set; }
[Display(Name = "Name")]
public string SchoolName { get; set; }
[Display(Name = "Date Added")]
public DateTime SchoolDateAdded { get; set; }
[Display(Name = "Last Update")]
public DateTime SchoolLastUpdate { get; set; }
[Display(Name="Updated By")]
public string SchoolUpdatedBy { get; set; }
//Referance Fields
public int DistrictId { get; set; }
public IEnumerable<SelectListItem> DistrictList { get; set; }
public int LocationId { get; set; }
public IEnumerable<SelectListItem> LocationList { get; set; }
public int TierId { get; set; }
public IEnumerable<SelectListItem> TierList { get; set; }
//Navigation Property
public District District { get; set; }
public Location Location { get; set; }
public Tier Tier { get; set; }
}
These IEnumerable<SelectListItem>s should not be part of your EF model. Remember the single responsibility principle. Keep any UI framework away from your DAL implementation. Use a view model representing a School.
As for the error, from EF's point of view, School has a 1-n association with SelectListItem, so it tries to make it part of its mapping schema. But each mapped type needs a primary key, which of course isn't mapped, and EF can't infer any.
A quick, but dirty, fix would be to exclude the properties from being mapped by the [NotMapped] attribute, but a better segregation of your code is the real remedy.

C# MVC Code First Complex Model

I have one table "Adverts" which stores basic info about adverts (eg: Name, Excerpt, Creation date...), and I need to store more detailed info in a separate table, But, here's my problem. Adverts can be different by type (sell, buy, rent, ...), category (residential, commercial, ...), so, detailed info is also different (eg: Commercial Advert don't need kitchen area property). I want to make few models which will describe detailed info for specific type or category
Here's my Adverts model:
[Table("Adverts_Adverts")]
public class Advert {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid AdvertId { get; set; }
public virtual Metadata Metadata { get; set; }
[Required]
[DataType(DataType.Text)]
public String Name { get; set; }
[DataType(DataType.Html), AllowHtml]
public String Content { get; set; }
[ForeignKey("Section")]
public Guid SectionId { get; set; }
public virtual Section Section { get; set; }
[ForeignKey("Category")]
public Guid CategoryId { get; set; }
public virtual Category Category { get; set; }
[ForeignKey("Type")]
public Guid TypeId { get; set; }
public virtual Type Type { get; set; }
public Decimal Price { get; set; }
[DataType("Enum")]
public Currency Currency { get; set; }
[ForeignKey("Details")]
public Guid DetailsId { get; set; }
public virtual ?????????? Details { get; set; }
[ForeignKey("User")]
public String UserId { get; set; }
public virtual User User { get; set; }
[ReadOnly(true)]
[DataType(DataType.DateTime)]
public DateTime Added { get; set; }
[ReadOnly(true)]
[DataType(DataType.DateTime)]
public DateTime Updated { get; set; }
public Int32 Views { get; set; }
[ReadOnly(true)]
public Status Status { get; set; }
...
}
here's my detailed info model for residential adverts:
[Table("Adverts_Details")]
public class ResidentialDetails {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid DetailsId { get; set; }
[ForeignKey("Advert")]
public Guid AdvertId { get; set; }
public virtual Advert Advert { get; set; }
[Required]
public Int32 Storeys { get; set; }
[Required]
public Int32 Floor { get; set; }
[Required]
public Int32 Rooms { get; set; }
[Required]
public Decimal TotalArea { get; set; }
[Required]
public Decimal LivingArea { get; set; }
[Required]
public Decimal KitchenArea { get; set; }
...
}
and this may be for commercial adverts:
[Table("Adverts_Details")]
public class CommercialDetails {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid DetailsId { get; set; }
[ForeignKey("Advert")]
public Guid AdvertId { get; set; }
public virtual Advert Advert { get; set; }
[Required]
public Int32 OfficesCount { get; set; }
[Required]
public Int32 Floor { get; set; }
[Required]
public Decimal TotalArea { get; set; }
...
}
So, how can I access both, ResidentialDetails and CommercialDetails, data within advert's property "Details"?
(Thank in advance)
This is an architecture problem, which is hard to answer without a complete understanding of your business rules. I can give you some general advice that will hopefully help you along.
As much as possible, remove complexity. I'm not sure what a "kitchen area property" is, but can you generalize it at all? Based upon context, you can call it something different, use it differently, etc. but if it's just a text field, then you can repurpose it in other contexts. Maybe for a residential advert it's "kitchen area" while maybe for commercial it's "break room area". (I really have no idea what this property is for, but I'm just trying to make the point that the same property can have a similar but slightly different meaning in different contexts).
If you can't generalize, then you'll need to start working on inheritance strategies. Create an object graph. How are these types and categories of adverts related. How are they different. Which ones are supergroups of others, etc.? Again, I don't know anything about the business rules at play, but maybe you need classes like Advert, ResidentialAdvert : Advert and CommercialAdvert : Advert. Then, you can add additional properties to these subclasses as necessary.
You'll also need to decide on a relational strategy. By default, EF will implement simple inheritance as STI (single-table inheritance, aka table per hierarchy or TPH for short). In other words, with the classes above, you would end up with an Adverts table with a Discriminator column. The value for this column would be one of "Advert", "ResidentalAdvert", or "CommercialAdvert", indicating which class should be instantiated, but all of the columns for all of the subclasses would reside in the same table. The benefit is that no joins are necessary, but the detriment is that all additional columns on your subclasses must be nullable or have default values. Other possible strategies would include, table per type (TPT), a compositional strategry, or table per concrete type (TPC), where every subtype gets its own unique table with all the fields from all supertypes.

Custom model & client validation in MVC without data annotations

I have the below view models, which are used to represent a survey of questions, but they are structured into a more flattened grid to accommodate the default model binder.
// Main ViewModel for the Question View
public class SurveyRowList
{
...
public IList<SurveyRow> SurveyRowList { get; set; }
}
public class SurveyRow
{
public int QuestionId { get; set; }
public int? ParentQuestionId { get; set; }
public int SurveyId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string HelpInformation { get; set; }
public int RenderOrder { get; set; }
public SurveyRowType RowType { get; set; }
// Collection of the same answer control, 1 or more times
// for each line number
public IList<AnswerControl> AnswerControls { get; set; }
}
public enum SurveyRowType
{
QuestionGroup = 1,
Question = 2,
AnswerRow = 3
}
public class AnswerControl
{
public int Id { get; set; }
public int QuestionId { get; set; }
// a reference to the database record answer id
public int SurveyAnswerId { get; set; }
// control type of checkbox, dropdown, input, dropdown-additional-textbox, checkbox-group
public ControlType ControlType { get; set; }
// used to specify getting particular backing data for dropdown and checkbox-group
public ControlSpecificType ControlSpecificType { get; set; }
public string Description { get; set; }
public string HelpInformation { get; set; }
public int RenderOrder { get; set; }
public bool InLine { get; set; }
public int LineNumber { get; set; }
public AnswerControlValueType Value { get; set; }
}
public class AnswerControlValueType
{
// Default string backing value when possible
public string Value { get; set; }
// AnswerCheckBox
public bool CheckValue { get; set; }
// AnswerCheckBoxListModal
public string ModalName { get; set; }
// AnswerMultiSelectListValue
public int[] ListValues { get; set; }
// making the options list setter public so that this data can be re-attached after model binding
public IEnumerable<SelectListItem> ListOptions { get; set; }
// AnswerImageValue
public HttpPostedFileBase Image { get; set; }
// AnswerSelectListAdditionalValue
public string AdditionalInformation { get; set; }
}
Each SurveyRow is like a row of a table. Only the SurveyRowType.AnswerRow actually makes use of the AnswerControls list.
Example of their ordering when rendered by their type and order number can be seen in this image:
The image only shows a few simple examples, and there can be 1-10 lines per page to a max of 100, but I have also added a bit of explanation of some of the validation rules I would want to apply. There are more but these are just a few examples.
My problem is that I want to support this more complex validation but all the rules and error text are stored in a database, 1. because of user configuration, 2. because of existing localisation of the error text to support several languages.
I am looking for any suggestions that people might have to be able to support this.
I have seen things like Fluent Validation and I haven't delved too deep yet but so far I can't see any examples that would specifically not use Data Annotations on a model.. and also RequiredIf or DisabledIf or EnabledIf style validation rules that apply across a slightly more complex collection of objects.
I worked with MVC patterns in 2001 with servlets, and again in 2006, with a custom MVC framework implemented on top of ASP.NET, and looking at what people are doing nowadays makes me believe that most did not even care about looking at what MVC stands for, only that explain the models nonsense. A lot of developers working with ASP.net MVC, tend to bind the data that is coming from the client to models, but that is such a poor design. Models contain the data that should be forwarded to the template manager which is in most cases the Razor engine.
http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
So my advice is: don't link the data that you get from the client into the models.
Get the data from the client, do a search on the Request object if it needs to
Validate the data (fluentvalidation)
apply the business rules
create the models
forward the models to the template engine
Also stopping using those crazy useless annotations.
My question was related to how I can support validating this complex model. I have since looked more at Fluent Validation and that has everything I need to do custom rules for a complex model, i.e. checking values across collections of objects within my model.

Updating/Insert 2 different models on same screen?

I am working on an edit view that allows a user to edit a particular entity. One of the drop down menus needs to allow the user to create a new instance of a different related entity if they don't see one that exists already. I'm trying to figure out the best practice for this.
The model they are editing:
public class SurveyProgramModel
{
[Key]
public int ProgramId { get; set; }
[DisplayName("Year")]
public int ProgramYear { get; set; }
[DisplayName("Status")]
public int ProgramStatusId { get; set; }
[DisplayName("Program Title")]
public string ProgramTitle { get; set; }
public int ProgramTypeId { get; set; }
[DisplayName("Program Type")]
public virtual SurveyProgramTypeModel ProgramType { get; set; }
[DisplayName("Status")]
public virtual ProgramStatusModel ProgramStatusModel { get; set; }
public virtual ICollection<SurveyResponseModel> SurveyResponseModels { get; set; }
}
If the user does not see a ProgramType that fits what they want, they need to have an empty text box that allows them to create a new ProgramType.
Here is the ProgramType model:
public class SurveyProgramTypeModel
{
[Key]
public int ProgramTypeId { get; set; }
[DisplayName("Program Type")]
public string ProgramType { get; set; }
public virtual ICollection<SurveyProgramModel> SurveyProgramModels { get; set; }
}
What is the best practice for updating 2 models in a single submission? How do I keep my view Strongly Typed to SurveyProgramModel? Create a View Model?
You already know the answer, you would create a View Model and make the necessary changes in your HttpPost action.

Categories

Resources