I have a project and user Model that inherits the default identity class.
These two share a many to many relationship.
public class Project
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<AppUser> users { get; set; }
}
public class AppUser : IdentityUser
{
public string DisplayName { get; set; }
public ICollection<Project> projects { get; set; }
}
I also have a project controller where I want to display ALL projects that contain the current user. (projects can have multiple users)
I also want to be able to create projects.
[Authorize]
public IActionResult Index(string id)
{
IEnumerable<Project> objProjectList = _unitOfWork.Project.GetAll();
return View(objProjectList);
}
I started off by passing the user id through an anchor tag like so.
<a class="nav-link text-dark"
asp-area="" asp-controller="Project" asp-action="Index"
asp-route-id="#UserManager.GetUserId(User)">Projects</a>
How do I use the id to get the projects that only contain the user corresponding to the id in my project controller?
How can I use the same id to create a project with the user attached on a post route in the same controller?
Should I avoid passing sensitive data like user id through anchor tags and get the user id some other way?
I'd appreciate any input, thanks.
You can try something like this . But is good idea to use ViewModels ti protect your database. Also, all your logic should be in Service classes not in the controllers. The way You pass the Id is totally fine.
public interface IProjectService
{
IEnumerable<Project> GetAllProjectsByUserId(object userId);
}
public class ProjectService : IProjectService
{
public IEnumerable<Project> GetAllProjectsByUserId(string userId)
{
return _unitOfWork.Project.Where(x => x.users.Any(x =>
x.Id = userId)).ToList();
}
}
Give the Service to the dependency container in the StartUp class
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IProjectService , ProjectService >();
}
Then you call the Service in the Controller
private readonly IProjectService projectService;
public ControllerName(IProjectService projectService)
{
this.projectService = projectService;
}
[Authorize]
public IActionResult Index(string id)
{
var viewModel = projectService.GetAllProjectsByUserId(id);
return View(objProjectList);
}
Тhere are more things to do, such as repositories, dtos eg, but this will be a good for the beginning
Related
This is my view model.
public class ProductViewModel
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
When form is posted from client the form is submitted to this Controller
public async Task<IHttpActionResult> AddProduct(ProductViewModel productViewModel)
{
await ServiceInstances.PostAsync("product/add", productViewModel);
return Ok();
}
Then this controller submit the form to the API controller
Which is on my separate Project.
[HttpPost]
[Route("add")]
public IHttpActionResult AddProduct(ProductViewModel model)
{
_productService.AddProduct(model.UserServiceDetails());
return Ok();
}
Extension UserServiceDetails Where i get the Login User Info
public static UserServiceDetailModel<T> UserServiceDetails<T>(this T model)
{
var serviceRequestModel = new ServiceRequestModel<T>()
{
Model = model,
LoginInfo = HttpContext.Current.User.Identity.GetUserLoginInfo();
};
}
AddProductService:
public void AddProduct(UserServiceDetailModel<ProductViewModel> serviceRequestModel)
{
var repo = _genericUnitOfWork.GetRepository<Product, Guid>();
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
mapped.Id = Guid.NewGuid();
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
repo.Add(mapped);
_genericUnitOfWork.SaveChanges();
}
Now my question is Is there any way to assign the value to this field CreatedDate and CreatedById before posting it to service?
Reduce these logic to mapper:
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
Or is there any way that those field gets mapped to Product when
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
Sometime i may have the List<T> on view-model and there i have to add this field using the loop.
So this same mapping may get repeated over and over on Add Method Or Update.
In some entity i have to assign the ModifiedDate and ModifiedById also.
My Mapper Configuration:
public class ProductMapper : Profile
{
public ProductMapper()
{
CreateMap<ProductViewModel, Product>();
}
}
I cannot add the Enitity as IAuditableEntity and Overrride in ApplicationDbContext because my DbContext is in separate Project and i donot have access to Identity there.
I have an Employee class, taken from a database. In that Employee class, I want to add a Manager object, which is also an Employee, based on the managerCode that is in the Employee database. See below for the class.
The managerCode is not defined as a key, . I don’t need recursion, i.e. I don’t need the Manager’s manager, etc. Just one level, the Employee and his manager.
Using .NET 4.5, c#, OData v4
I am using OData to send back the Employee, but the Manager part isn’t added in the response, even if it’s there in the object I try to send.
What am I missing? Something in the WebApiConfig?
Thanks
Employee class, first 4 fields are directly taken from database.
Class Employee
{
[Key]
public int id { get; set; }
public string employeeCode { get; set; }
public string employeeName { get; set; }
public string managerCode { get; set; }
[NotMapped]
public Employee Manager { get; set; }
}
Controller class. GetEmployeeById(id) will get Employee(s) with their Manager.
[HttpGet]
[EnableQuery]
[ODataRoute("employeeById(id={id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
WebApiConfig
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("ODataRoute", "odata",GetEdmModel(),
new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "employee_odata";
builder.ContainerName = "employee_odataContainer";
builder.EntitySet<Employee>("Employee");
builder.EnableLowerCamelCase();
var unboundGetEmployee = builder.Function("employeeById");
unboundGetEmployee.Returns<Employee>();
unboundGetEmployee.Parameter<int>("id");
unboundGetEmployee.Namespace = "employee_odata.Functions";
return builder.GetEdmModel();
}
SOLUTION
Remove unboundGetEmployee from WebApiConfig (not needed).
Make Manager item in Employee class virtual, without the [NotMapped]:
public virtual Manager Manager { get; set; }
Controller:
[EnableQuery]
[ODataRoute("Employee({id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
//handle data return normally...
//I have to detect $expand=Manager, to fetch the actual Manager object.
//otherwise it's null (not linked with primary key)
}
With these fee changes, $expand is working well.
You need to add $expand to show navigation property, like
localhost\odata\employee_odata.Functions.employeeById(id=1)?$expand=Manager
And for get employee by Id, I suggest you to use this method in controller:
[EnableQuery]
public IHttpActionResult Get(int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
Then request like localhost\odata\Employee(1) can route to that method.
I have two models as below
public class Category
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
[Required]
public string category { get; set; }
[Required]
public string Desc { get; set; }
}
public class Product
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
public int CatID { get; set; },
[ForeignKey("CatID")]
public virtual Category Category { get; set; },
[Required]
public string Desc { get; set; },
public string DisplayName
{
get
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
}
}
This is my Edit Action
public ActionResult Edit(int id)
{
ViewBag.PossibleCategories = categoryRepository.All;
return View(productRepository.Find(id));
}
[HttpPost]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid) //<== This becomes false saying category.desc is required
{
productRepository.InsertOrUpdate(product);
productRepository.Save();
return RedirectToAction("Index");
}
else
{
ViewBag.PossibleCategories = categoryRepository.All;
return View();
}
}
I have a scaffolded a Edit view of product and it shows ID and DisplayName as Readonly. All the other fields a editable.
The edit view also has the product -> category -> category has a read-only text field
#Html.TextBoxFor(model => model.Category.category, new Dictionary<string, object>() { { "readonly", "true" } })
The Post back sends this and tries to create a new category. This is not required. The category link will be carried forward using the product.CatID.
How can i display these types of fields??
When the Edit view Post back the Model state appears as invalid because the product's category's desc is null (product -> category -> desc).
if i comment out the DisplayName property in Product this issue doesn't occur.
From my understanding, this is because the DiaplayName property refers to Category property and the view view doesn't have category.desc field so when the model is created back on the POST action, the desc is not populated. Adding the category.desc field to the view is one way of solving this problem.
Is there any other method to solve this?
Note: This is not the only model i'm having this issue. There are many complex models which have the same problem and to me having these fields also included in the view would make for (1) a very cluttered view (2) the amount of data making the round trip will be high.
Simple Solution
Check for null. Really you should be making this a habit anyway.
public string DisplayName
{
get
{
if(this.Category != null)
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
else
{
return String.Empty;
}
}
}
Complex Solution
Instead of directly using your database model in your Views another solution is to create ViewModels. These are models meant specifically for your View. As a simplified example, let's take your Product model and create a ViewModel.
Create a folder for your ViewModels
Create ViewModel files that match your Controller
Create a ViewModel that you will use in your View
Say you have a Store Controller. This would be the file structure you would create.
Models
ViewModels
StoreViewModels.cs
Inside the StoreViewModels you would create a ViewModel called ProductViewModel which you would fill in with information from Product.
public class ProductViewModel
{
public int ID { get; set; }
public string Description { get; set; }
public string DisplayName { get; set; }
public ProductViewModel() { }
public ProductViewModel(Product product)
{
this.ID = product.ID;
this.Description = product.Description;
this.DisplayName = product.DisplayName;
}
}
In your View you reference ProductViewModel instead of Product. On the receiving end you then translate the ViewModel fields back to your Model. Let me know if you have any questions.
I have an ASP.NET MVC 3 application.
I have a Model, ViewModel, View, Controller.
I use Ninject as IoC.
My Controller uses a ViewModel to pass data to the View.
I've started to use Services (concrete and interface types) to take information from the ViewModel and query it against the database to manipulate it.
Can I use the same Service to setup the ViewModel? Or is this going against the grain of the design pattern?
I.e. Can I abstract setting up the ViewModel in the Service layer?
Scenario
The scenario is; my Model has lots of references to other Models, so when I setup the ViewModel in the controller it's to verbose, and I feel the Controller is doing too much. So I want to be able to just do something like:
var vm = _serviceProvider.SetupViewModel(Guid model1Id, Guid model2Id, /*etc..*/)
And the SetupViewModel function in the ServiceProvider would look like this:
public MyModelViewModel SetupViewModel(Guid model1Id, Guid model2Id, /*etc...*/)
{
var vm = new MyModelViewModel();
var model1 = _repository.Model1s.FirstOrDefault(x => x.Id.Equals(model1Id));
var model2 = _repository.Model2s.FirstOrDefault(x => x.Id.Equals(model2Id));
// etc....
vm.Model1 = model1;
vm.Model2 = model2;
return vm;
}
By doing this I could also add some null conditions as well, not worrying about making my Controller really really really big!!
I use 1 ViewModel for the Create/Edit actions. I don't reuse the ViewModel elsewhere.
I would let the service layer return a Domain Model and map it to a ViewModel in the controller.
This way you can use a service method with multiple ViewModels, for a desktop and mobile view for example.
You can let AutoMapper do the hard work for you or do it manually, by creating a constructor in the ViewModel which takes the Domain Model.
The domain model:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Telephone { get; set; }
public string Email { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
The ViewModel:
public class CustomerWithOrdersModel
{
public CustomerWithOrdersModel(Customer customer)
{
Id = customer.Id;
FullName = string.Format("{0}, {1}", customer.LastName, customer.FirstName);
Orders = customer.Orders.ToList();
}
public int Id { get; set; }
public string FullName { get; set; }
public IEnumerable<Order> Orders { get; set; }
}
EDIT: AutoMapper example:
The AutoMapper profile containing the mapping from a Customer to a CustomerWithOrdersModel:
public class ViewModelProfile : Profile
{
public override string ProfileName
{
get { return "ViewModel"; }
}
protected override void Configure()
{
CreateMap<Customer, CustomerWithOrdersModel>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => string.Format("{0}, {1}", src.LastName, src.FirstName)))
.ForMember(dest => dest.Orders, opt => opt.MapFrom(src => src.Orders.ToList()));
}
}
Id is mapped by convention.
Extension methods for the ViewModelProfile:
public static class ViewModelProfileExtensions
{
public static CustomerWithOrdersModel ToModel(this Customer customer)
{
return Mapper.Map<CustomerWithOrdersModel>(customer);
}
public static Customer ToEntity(this CustomerWithOrdersModel customerWithOrdersModel)
{
return Mapper.Map<Customer>(customerWithOrdersModel);
}
}
The controller action:
public ActionResult Details(int customerId)
{
Customer customer = _customerService.GetById(customerId);
CustomerWithOrdersModel customerWithOrders = customer.ToModel();
return View(customerWithOrders);
}
If you create a mapping from CustomerWithOrdersModel to Customer, you can use customerWithOrdersModel.ToEntity() to map it back to the Domain Model.
Thats it! You can remove the constructor with the Customer domain model from the ViewModel.
If you have the view models as their own project and handle the mapping and returning of view models in your service layer, I see nothing wrong with that. For separation of concerns you could always have another component that handles the mapping.
I'm having a real trouble with what I need to do.
Here's the thing:
I'm creating Silverlight Business Application. I want users to be able to define their own "reminders" and "templates". It seems very simple, just 3 models, 2 one-to-many relations and that's all. But I have no idea how I can connect the existing User model to other models.
I tried to create my own "membership" provider - I've created db with all 3 models and it seemed to be ok, I created EntityModel, but now I have 2 different places where User class is defined, and in the first one it inherits UserBase class and in another EntityObject (in the file Model.Designer.cs, which is generated automatically.
I'm totally confused - can I stick with the EntityObject solution, delete other definitions of classes? If so, how can I still be able to use all the features that come with silverlight business application? (Authentication/Registering etc. is already provided).
We have implemented this scenario in our LOB app.
Firstly add the appropriate properties to the user class like so.
public partial class User : UserBase
{
public Guid UserId { get; set; }
public int PeopleId { get; set; }
public int EpothecaryUserId { get; set; }
public string PersonFullName { get; set; }
public SearchGroups SearchGroups { get; set; }
public string SearchHistoryString { get; set; }
public int SearchRowsReturnedPerGroup { get; set; }
}
Then create a class derived from AuthenticationBase
public class AuthenticationService : AuthenticationBase<User>
{
protected override User GetAuthenticatedUser(IPrincipal principal)
{
return base.GetAuthenticatedUser(principal).WithProfile();
}
[Invoke]
public void SaveMyUser(User user)
{
if (user.UserId == Guid.Empty)
{
ClientLogger.Error("SaveMyUser failed because the UserId is invalid");
return;
}
using (var db = new Pharma360Model())
{
var userProfile = db.UserProfiles.Single(p => p.EpothecaryUserId == user.EpothecaryUserId);
userProfile.SearchGroups = (int)user.SearchGroups;
userProfile.SearchHistory = user.SearchHistoryString;
userProfile.SearchRowsReturnedPerGroup = user.SearchRowsReturnedPerGroup;
db.SaveChanges();
}
}
}
And this will take care of the loading and saving of the custom User class.