ASP MVC 5 Organizing Project To Avoid Duplicating Code - asp.net-mvc

I am building an asp mvc 5 support ticket helpdesk application. The application will require users to signup to submit a support ticket. Users actions would be to update the ticket by adding notes like a thread.
Agents will manage the ticket and have additional permissions to edit the ticket and create internal as well as public notes and be able to view all tickets.
There is a site admin who can configure the site.
The issue is if I have a controller say called ticket controller, the main actions on the create or edit ticket are similar for both an agent and a user. The differences are that when a user creates a ticket they can only complete certain fields on the form where as an agent can update additional fields.
So what I'm ending up with is a lot of if then else statements in my controllers and views to check user roles inorder to restrict what form fields and actions are displayed to the different types of users. Also my viewmodels are exposing properties to a customer that they shouldn't have permissions to.
So my question is how do you organize this type of set up. Do you create a separate area for Agents and duplicate all the controllers and viewmodels to cater for this type of user.
Or would you just bloat the controllers and views with if then else statements for every different type of user role that is created?
Some advise on what would be best practice for this particular application would be appreciated.
The only benefit of creating an Area for agents is that I could drastically change the look and feel of the Agent portal, have a different url eg. mysite/agentdesk/tickets and it would be easier to maintain but at the expense of duplicating code.
/* Current Controller Logic and view model */
As you can see the in viewmodel exposes the status and priority properties which only an agent can set.
In the create method of the controller, you can see the teneray operator being used to determing if the user has the correct role to set the above properties.
public class TicketViewModel
{
public int Id { get; set; }
[Required]
public string Subject { get; set; }
[Required]
public string Description { get; set; }
// Only an agent can change the status
[Required]
public int Status { get; set; }
// Only an agent can change the priority
[Required]
public int Priority { get; set; }
public IEnumerable<StatusType> StatusTypes { get; set; }
public IEnumerable<PriorityLevel> PriorityLevels { get; set; }
}
public class TicketController : Controller
{
[Authorize]
public ActionResult Create()
{
var viewModel = new TicketViewModel
{
StatusTypes = _context.StatusTypes.ToList(),
PriorityLevels = _context.PriorityLevels.ToList()
};
return View(viewModel);
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(TicketViewModel viewModel)
{
if (!ModelState.IsValid)
{
viewModel.StatusTypes = _context.StatusTypes.ToList(),
viewModel.PriorityLevels = _context.PriorityLevels.ToList()
return View(viewModel);
}
bool agentRole = HttpContext.Current.User.IsInRole("Agent");
var ticket = new Ticket
{
CreatedBy = User.Identity.GetUserId(),
Subject = viewModel.Subject,
Description = viewModel.Description,
StatusId = agentRole ? viewModel.Status : 1,
PriorityId = agentRole ? viewModel.Priority : 1
};
_context.Tickets.Add(ticket);
_context.SaveChanges();
return RedirectToAction("Index", "Tickets");
}
}

I think an area would be the best way to go. The simple fact is that the complexity of your code is going to quickly spiral out of control otherwise. It's one thing to just show/hide form fields based on some role, but really, for security, this needs to be protected end-to-end. In other words, you would also need to check inside your action that properties are only being set with values if the user is allowed to access those properties. It's trivial to post things you shouldn't be able to edit, even if no form fields exist to allow that.
To get around the code-duplication issue, just don't duplicate code. That sounds a little trite, but there it is. Similar code in your action can be factored out into a base controller or just some helper class. Similar code in your views can be factored out into partials. Basically, anything that would be the same for both situations should go somewhere where the same code can be used in both situations.

Related

With MVC/Entity Framework, how does one manage housekeeping tasks in the view?

My understanding is that only one model can be passed to the view at a time. The problem with this that I see is that I am being forced to pass the Entity Framework model, and not any model that will manage housekeeping in the view. Here is what I mean:
You need to make a page that allows someone to submit Cars to the database. Along with the form fields (e.g. CarName, CarMake, CarYear) you also need a checkbox at the bottom of the page called "Remember Values" which when checked will "remember" the form values when the user clicks the Submit button at the bottom, so when they return all of their form data is still in the form fields. Needless to say, this Remember Values variable is not part of the Entity Framework model- it is just a housekeeping variable for use in the view.
How would you guys handle adding this checkbox? It feels like it should be part of the model, but I can't send two models to the view. Am I just looking at this issue wrong? What would you recommend?
.NET 4.5/MVC 5/EntityFramework 6
This is a good situation to be using ViewModels.
Build your ViewModels with all properties that you'd want to send/retrieve to/from your view. For example:
EF Entity
public class Car {
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Make { get; set; }
public virtual string Year { get; set; }
}
View Model
public class AddCarViewModel {
public Car Car { get; set; }
public bool RememberValues { get; set; }
}
Controller
public class CarController : Controller {
// Constructor....
public ActionResult Add() {
var vm = new AddCarViewModel();
return View(vm);
}
[HttpPost]
public ActionResult Add(AddCarViewModel vm) {
if (ModelState.IsValid) {
_carService.Save(vm.Car);
}
return View(vm);
}
}
Another good approach is to create Domain Transfer Objects, which are POCO classes to hold data that is transferred through the pipes. For example, in your business layer you may want to audit any changes to your Car model. So you may have properties like CreatedBy, CreatedDate, UpdatedBy, UpdatedDate, etc. (These properties are generally never displayed to the end-user but are important to store).
So you'd create the following classes:
public class Car {
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Make { get; set; }
public virtual string Year { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual DateTime UpdatedDate { get; set; }
}
public class CarDTO {
public Guid Id { get; set; }
public string Name { get; set; }
public string Make { get; set; }
public string Year { get; set; }
}
and you can use a library such as AutoMapper to map properties from Car -> CarDTO:
var car = _carService.GetCarById(id);
var carDTO = Mapper.Map<Car, CarDTO>(car);
This way, you can choose which properties you want exposed to your views by utilizing DTO's.
I always create an additional model that I can convert to and from between the EF model.
This additional model gets passed to the View and holds al the neccesary properties like CarName, Carmake, CarYear, Remember and probably most importantly, the Id of that particular object.
So when the user submits, this model gets passed to the Post method where you can extract all the required properties. You fetch the database model using the Id from your DbContext and update the properties with the values that were just passed through.
Technically you can send two models to the view, if the model is actually something like a Tuple:
#model Tuple<SomeEFModel, SomeViewModel>
Though that's kind of ugly. And if you're creating a view model anyway you might as well just make it a composite of the Entity Framework model. Something like:
public class SomeViewModel
{
public SomeEFModel EFModel { get; set; }
public string SomeOtherProperty { get; set; }
// other stuff...
}
Then just build an instance of that in the controller and send it to the model:
#model SomeViewModel
You could even just de-couple the EF model and the view model entirely, creating a custom view model that has everything for that view and then translating to/from that and the EF model at the controller level. Ultimately it comes down to what implementation looks cleaner and is easier to maintain, which can differ from one context to another.
Edit: Another option, if the models get unwieldy for whatever bits of the framework you're relying on, could be to separate your outgoing and incoming models. For pushing data to the view, you can use the composite view model above. But then when the data comes back from the view just use a normal Entity Framework model and a couple of additional parameters for your additional fields:
public ActionResult Edit(int id)
{
// build the view model with the EF model as a property
return View(someViewModel);
}
[HttpPost]
public ActionResult Edit(SomeEFModel model, string someOtherProperty)
{
// here you have an EF model from the view like normal
// plus the additional property (however many you need)
// you can even create a separate view model to collect the other properties
// as long as the names are well defined, the model binder should build both
}
First, you absolutely should NOT be passing your EF models directly to your view, and you should certainly NOT be posting directly to your EF models. This is essentially taking untrusted, unsanitized input and directly writing it to your data model with only bare minimal validation.
While this may work with simple models with no security or other ramifications, imagine a situation where you allowed a user to edit his profile information. Further, imagine that in his profile you also stored information relating to his subscription information. A specially crafted submit could alter his subscription information and give himself free access to your site, or worse...
Instead, you use view models. Apart from the security aspects, view models are good because other than in very simple CRUD style sites, your views requirements are typically different from your data models requirements. For instance, a particular field might be nullable in your data model, but you might want to make it required in your view. If you pass your model directly, then you can't do that easily.
Finally, Aggregate view models aggregate many different submodels to provide an overall model for the view, which is what you are getting at. You would then use a service layer, repository, or business logic layer to translate your view model to your data model, massaging data or applying logic as needed.

Using an MVC Model as a filter in the repository

I have a details view that is typed to IEnumerable. The view with a bunch of drop downs that let you add filters to the list of records rendered.
All these dropdowns correspond to properties on the MVC model:
public class Record
{
public string CustomerNumber { get; set; }
public string CustomerName { get; set; }
public string LineOfBusiness{ get; set; }
public DateTime? Date { get; set; }
}
Now, I'm using my model as my dto to shuffle data between my controller and my repo. Since all my drop down filters represent the model properties, I pass my model to a repo retrieval method, check its properties and filter based on its values? In other words:
public IEnumerable<TradeSpendRecord> Get(TradeSpendRecord record)
{
IQueryable<tblTradeSpend> query = _context.tblRecords;
if (!String.IsNullOrEmpty(record.CustomerName))
query = query.Where(x => x.CustomerNumber == record.CustomerNumber);
if (!String.IsNullOrEmpty(record.LineOfBusiness))
query = query.Where(r => r.LOB == record.LineOfBusiness);
SNIP
Hope this isn't too subjective, but I'm wondering if anyone has any input about whether this is a good/bad practice. I haven't seen a whole lot of examples of dynamic filtering like I need to do, and am looking for some guidance.
Thanks,
Chris
If you're doing what I think you're doing, I'm not sure this is the best way of doing it.
Keep your 'Models' in your MVC/presentation layer (whether this is one physical assembly or not) dedicated to your presentation layer. The only things that should be touching them are your Views and your Controllers. You don't want what should be independent entities to be so tightly coupled to your View Models.
I'd suggest creating a separate TradeSpendFilter class, which, at its simplest, exposes the filterable properties of your domain entity (likely more than any given View Model). You'd then pass this into your "filtering service" or whatever it may be. This also means you can extend your filtering functionality independent of both your domain models and your MVC app. For example, if you suddenly want to filter multiple objects, you can simply change...
public class TradeSpendFilter
{
public string CustomerName { get; set; }
...
}
...to...
public class TradeSpendFilter
{
public IEnumerable<string> CustomerNames { get; set; }
...
}
... without causing all sorts of problems for your MVC app.
Additionally, it will also mean you can make use of your filtering functionality elsewhere, without tying further components to your MVC app and ending up in a bootstrapped mess.

T4 Self Tracking Entities And Model Validation

I'm using EF4.x Self Tracking Entities in my project and am trying to achieve Model Validation in my MVC4 web application, however, my Model State always seems to be Valid. I'm using T4 templates to generate my "buddy" classes. Below is an example of one of the STE's and its buddy:
STE - generated using T4:
[DataContract(IsReference = true)]
[KnownType(typeof(Filing))]
public partial class FilingHistory: IObjectWithChangeTracker, INotifyPropertyChanged
{
public int FilingHistoryId
{
//Snipped for Brevity
}
// Navigation, ChangeTracking, Association Fix up snipped
}
Here is the Buddy Class generated also via a T4 template I wrote:
[MetadataType(typeof(FilingHistoryMetaData))]
public partial class FilingHistory
{
// Partial Class
}
public class FilingHistoryMetaData
{
[Display(Name = "Filing History Id")]
[Required(ErrorMessage = "Filing History Id is Required.")]
int FilingHistoryId { get; set; }
// Other properties snipped for Brevity
}
I'm going to exclude the key's from each MetaData class because those will be created automatically (just as an fyi). Also, the namespaces for the STE, the empty partial and the buddy class are identical
When I create a simple controller in MVC4 just to test it out, with a Create Template, on the HttpPost Action of Create I have some code as shown below:
[HttpPost]
public ActionResult Create(FilingHistory filingHistoryToCreate)
{
if (ModelState.IsValid) // THIS IS ALWAYS TRUE! even if i pass nothing<----
{
return Redirect("/");
}
return View(filingHistoryToCreate);
}
I read through a bunch of SO links and even went through MSDN, and I think I have everything setup correctly, i.e. namespaces are fine so there is no naked partial class stuff going on.
When my view renders I leave all the textboxes empty, I set a breakpoint to inspect by entity and nothing has been set, yet the model is valid. I also tested by entering some garbage into the textboxes to ensure the model binding was working fine, and it was...
I tried also testing using a console application, and I found out that you have to take an additional step of registering the MetaData type, but I beleive in MVC this isnt required. In case it helps anyone - the console app and registering meta data type didn't work for me either, so I'm thinking my buddy class may be the culprit?
It seems the DataAnnotationsModelMetadataProvider is looking for the public properties when checks for the attributes.
Change your FilingHistoryId to public and it should work:
public class FilingHistoryMetaData
{
[Display(Name = "Filing History Id")]
[Required(ErrorMessage = "Filing History Id is Required.")]
public int FilingHistoryId { get; set; }
// Other properties snipped for Brevity
}

Alternative User management in ASP.NET MVC

I am in the planning phase of a new ASP.NET MVC application and one of the requirements is storing some user information that is not part of the standard set found in the User class that comes with ASP.NET MVC. I suppose it comes down to two questions.
1) Can I edit the class that is being used already to store the information that I need?
2) If I roll my own how can I keep things like the Authentication piece that make things so nice when trying to lock down some views using the User.IsAuthenticated method?
Another alternative I have considered is using the User class provided as is, and instead putting the other information into a separate table with the guid userid as the foreign key.
Suggestions?
Profiles are one option as #Burt says, and offers a lot of flexibility.
I had a similar need to track Employee information, but I opted to roll my own Employee class and create a relationship to a standard User. I really like how this has worked out as I can keep any Employee specific business logic separate from the User class Membership system.
Since not every User was going to be bound with an employee, this made more sense for my case. It may not for yours, but it is an alternative.
So, I have something like:
public class Employee
{
public Employee(string name) : this()
{
Name = name;
}
public virtual string Name { get; set; }
public virtual string Title { get; set; }
public virtual decimal Salary { get; set; }
public virtual decimal Hourly { get; set; }
public virtual decimal PerDiem { get; set; }
public virtual string StreetAddress { get; set; }
public virtual Guid UserId { get; set; }
public virtual MembershipUser User {
get
{
// note that I don't have a test for null in here,
// but should in a real case.
return Membership.GetUser(UserId);
}
}
}
See ASP.Net MVC Membership Starter Kit. It provides the Asp.Net MVC controllers, models, and views needed to administer users & roles. It will cut distance in half for you.
Out of the box, the starter kit gives you the following features:
List of Users
List of Roles
User
Account Info
Change Email Address
Change a User's Roles
Look into profiles that are part of the membership functionality provided by MS. They are extendable and pretty flexible.

ASP.NET MVC / DDD architecture help

I am creating a Web application using ASP.NET MVC, and I'm trying to use domain-driven design. I have an architecture question.
I have a WebControl table to store keys and values for lists so they can be editable. I've incorporated this into my business model, but it is resulting in a lot of redundant code and I'm not sure it belongs there. For example, in my Request class I have a property called NeedType. Because this comes from a list, I created a NeedType class to provide the values for the radio buttons. I'm showing just one example here, but the form is going to have probably a dozen or so lists that need to come from the database.
[edit, to clarify question] What's a better way to do this? Are these list objects really part of my domain or do they exist only for the UI? If not part of the domain, then they don't belong in my Core project, so where do they go?
public class Request : DomainObject
{
public virtual int RequestId { get; set; }
public virtual DateTime SubmissionDate { get; set; }
public virtual string NeedType { get; set; }
public virtual string NeedDescription { get; set; }
// etc.
}
public class NeedType : DomainObject
{
public virtual int NeedTypeId { get; set; }
public virtual string NeedTypeCode { get; set; }
public virtual string NeedTypeName { get; set; }
public virtual int DisplayOrder { get; set; }
public virtual bool Active { get; set; }
}
public class RequestController : Controller
{
private readonly IRequestRepository repository;
public RequestController()
{
repository = new RequestRepository(new HybridSessionBuilder());
}
public RequestController(IRequestRepository repository)
{
this.repository = repository;
}
public ViewResult Index(RequestForm form)
{
ViewData.Add("NeedTypes", GetNeedTypes());
if (form == null)
{
form = new RequestForm();
form.BindTo(repository.GetById(125));
}
}
private NeedType[] GetNeedTypes()
{
INeedTypeRepository repo = new NeedTypeRepository(new HybridSessionBuilder());
return repo.GetAll();
}
}
Create a seperate viewmodel with the data you need in your view. The Model in the M of MVC is not the same as the domainmodel. MVC viewmodels are dumb DTO's without behaviour, properties only. A domain model has as much behaviour as possible. A domain model with get;set; properties only is considered an anti-pattern called "anemic domain model". There are 2 places where most people put the viewmodels: in the web layer, close to the views and controllers, or in a application service layer.
Edit:
When you only need to display a list of all needtypes in the database and one request in your view, I would indeed create one viewmodel with the request and the list of needtypes as properties. I don't think a call to multiple repositories in a controller is a smell, unless you have a larger application and you might want a seperate application service layer that returns the whole viewmodel with one method call.
I think it might also be a good idea to follow the advise of Todd Smith about value object.
When the needtypes can be added or edited by users at runtime, needtype should be an entity. When the needtypes are hardcoded and only changed with new releases of the project, needtype should be a value object and the list of needtypes could be populated by something like NeedType.GetAll() and stored in the database by adding a column to the request table instead of a seperate needtype table.
If it comes from a list, then I'm betting this is a foreign key. Don't think about your UI at all when designing your domain model. This is simply a case where NeedType is a foreign key. Replace the string NeedType with a reference to an actual NeedType object. In your database, this would be a reference to an id.
When you're building your list of NeedType choices, you simply need to pull every NeedType. Perhaps keeping it cached would be a good idea if it doesn't change much.
Your NeedType looks like a value object to me. If it's read-only data then it should be treated as a value object in a DDD architecture and are part of your domain.
A lot of people run into the "omg so much redundancy" issue when dealing with DDD since you're no longer using the old Database -> DataTable -> UI approach.

Resources