Our team is currently developing a web application, in that we have a class library with Entity Framework .edmx added and have generated the POCO classes.
Our Web Application is based on MVC, we have defined our classes in models with the same name and attributes (copy paste of the POCO classes from the .edmx). The .edmx class library is refrenced to MVC web application.
The Views are strongly typed of MVC Model classes. We have used MVC Models for Display, StringLength & Required.
In our controller when there is a CRUD operation we are accepting the POCO Classes Type such as
public ActionResult Create(EFModels.User user) { }
EFModels.User is a class from the .edmx (POCO generated class) and the MVC View is strongly typed to the model which is MvcWebApplication.Models.User.
Question is how are we getting data from the MvcWebApplication.Models.User (from Model) to EFModels.User (EF class) in the ActionResult Create ??
I am able to get the data, I know it is coz of the same property name. I tried changing the class name but still it works, but if we change the property name it does not work. I cannot understand the logic behind it.
Initially we never knew it didn`t work and we were using AutoMapper to convert the Model Class to Edmx POCO class.
Any ideas, Thanks.
The question is how are we getting the values of the Model Class to the EF class with any mapping. I don`t need to use AutoMapper, without using that I am getting the values.
Have a look at the code, hope that explains better...
//POCO CLASS
namespace EFModels
{
using System;
using System.Collections.Generic;
public partial class User
{
public int Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
}
}
//MVC Model Class
namespace MvcWebSamp.Models
{
public class User
{
public int Id { get; set; }
[Display(ResourceType = typeof(BasicTags), Name = "Type")]
[StringLength(15, ErrorMessageResourceName = "TypeLength", ErrorMessageResourceType = typeof(BasicTags))]
[Required(ErrorMessageResourceName = "TypeRequired", ErrorMessageResourceType = typeof(BasicTags))]
public string TypeName { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
}
//MVC VIEW PAGE
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcWebSamp.Models.User>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
User
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>User</h2>
<% using (Html.BeginForm("Create", "User", FormMethod.Post))
{
%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>User</legend>
<div class="editor-field">
<%: Html.LabelFor(model => model.TypeName) %>
<%: Html.EditorFor(model => model.TypeName)%>
<%: Html.ValidationMessageFor(model => model.TypeName)%>
</div>
<div class="editor-field">
<%: Html.LabelFor(model => model.Name) %>
<%: Html.EditorFor(model => model.Name)%>
<%: Html.EditorFor(model => model.Address.street)%>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
</asp:Content>
//Controller Method
public ActionResult Create(EFModels.User user)
{
Model1Container con = new Model1Container();
con.Users.Add(user);
con.SaveChanges();
return View("User");
}
When I hit the Create Button, I am posting data of the type MvcWebSamp.Models.User and in the Create Action I am able to get the data of the type EFModels.User user without using any AutoMapper. I want to know how this works???
You should be using your view model as the argument type for your create method:
[HttpPost]
public ActionResult Create(UserViewModel model)
{
if (ModelState.IsValid)
{
int id = UserService.CreateFromViewModel(model);
return RedirectToAction("View", new { id });
}
return View(model);
}
You controller should be designed to create and accept view models, and it passes those to an appropriate service which interacts with your data layer to create your domain model. This keeps your controller action quite thin.
You can use something like AutoMapper in your service to easily map between your view model and your domain model:
var user = Mapper.Map<UserViewModel, User>(model);
By giving DbContext to UI Layer you are creating dependancy between UI and database. Try to seperate it and use repository pattern and dependency injection.
Reference:
http://blogs.msdn.com/b/adonet/archive/2009/06/16/using-repository-and-unit-of-work-patterns-with-entity-framework-4-0.aspx
http://prodinner.codeplex.com/releases/view/66899
You aren't using your MvcWebSamp at all - as you can see, the controller takes the EFModel
public ActionResult Create(EFModels.User user)
It works because the properties are the same. You just need to modify the controller method signatures to take the MvcWebSamp objects instead, and then transform those objects to the EFModel objects.
Automapper should work. We use it all the time even with different property names. Please post usage of automapper that does not work for you. Otherwise see following post to make it work with different property names.
Usage of Automapper when property names are different
In order to use the Entity Framework, you need to create an Entity Data Model. For adding Entity Model:
1) Right click on Model Folder in the solution explorer.
2) Select Add a New Item.
for more details please check out the following link....
http://www.mindstick.com/Articles/6f3bb3c6-d195-487b-8b82-244bb417b249/?Model%20classes%20with%20Entity%20Framework%20in%20MVC
Thanks !!!
Related
I am new to MVC and trying to understand viewmodels.
I have Staff, Service, BookingSlot, Appointments and the ApplicationUser entities. I have the following viewmodel:
public class AppointmentBookingViewModel
{
[Display (Name ="Select Staff")]
public int StaffId { get; set; }
public IEnumerable<Staff> Staffs { get; set; }
[Display(Name = "Select Service")]
public int ServiceId { get; set; }
public IEnumerable<Service> Services { get; set; }
[Display(Name = "Select Slot")]
public int BookingSlotId { get; set; }
public IEnumerable<BookingSlot> BookingSlots { get; set; }
}
This is the controller:
public class AppointmentBookingController : Controller
{
private readonly SalonContext _context;
private AppointmentBookingViewModel _appointmentBookingViewModel = new AppointmentBookingViewModel();
public AppointmentBookingController(SalonContext context)
{
_context = context;
ConfigureViewModel(_appointmentBookingViewModel);
}
public void ConfigureViewModel(AppointmentBookingViewModel appointmentBookingViewModel)
{
appointmentBookingViewModel.Staffs = _context.Staffs;
appointmentBookingViewModel.Services = _context.Services;
appointmentBookingViewModel.BookingSlots = _context.BookingSlots;
}
// GET: AppointmentBooking
public ActionResult Index()
{
return View(_appointmentBookingViewModel);
}
}
My question is, how can I create a form in the view and post the data to the Appointments table, the following doesn't work.
#model HairStudio.Services.ViewModels.AppointmentBooking.AppointmentBookingViewModel
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="row">
<div class="col-12">
<form asp-action="Create">
<div class="form-group">
<label asp-for="ServiceId" class="control-label"></label>
<select asp-for="ServiceId" class="form-control"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
You already directed your form to action called "Create" with asp-action attribute, but there is no such action in your controller. Submitting a form sends a HTTP POST request, which needs to be handled by your controller. Therefore, add a Create() method in your AppointmentBookingController:
// POST: Create
public IActionResult Create(AppointmentBookingViewModel appointmentViewModel)
{
if (!ModelState.IsValid)
{
// Server side validation of form has failed.
// Return to the calling view and inform the user about the errors.
return View(appointmentViewModel, "Index");
}
return View(appointmentViewModel, "<NAME_OF_YOUR_CREATED_APPOINTMENT_VIEW>");
}
Consider redirecting after successfully accepting a HTTP POST request according to a design pattern Post/Redirect/Get.
Also, take a look at this part of ASP.NET Core documentation about working with forms. I'm sure you'll find there something of value.
There's nothing magical about a view model. It's just a class. The idea is that the entity class (i.e. the thing you're persisting to the database via Entity Framework) should be concerned only with the needs of the database. A view can and often does have an entirely different set of needs, so you create a class specifically for that: the view model. This is just basic SRP (single-responsibility principle): a single class shouldn't try to do too much.
Then, you simply need a way to bridge the two. In other words, you need to copy values from the entity to the view model and vice versa. That process is called mapping, and can be achieved in a number of different ways. The most common approach is to use a third-party library like AutoMapper. However, you can also just manually map over each value or even use something akin to the factory pattern, where you have another class that holds the knowledge for how to do the mapping and can spit out an entity from a view model and vice versa.
Now, it's not really possible to give you exact guidance because we don't have your entity(ies), but you seem to be wanting to pick a particular Staff, Service and BookingSlot and associate that with the Appointment you're creating. It's not critical, but for efficiency, you should not be carrying around the full set of all these entities on your view model. All you need is an IEnumerable<SelectListItem>, which allows you to use much more efficient queries:
Instead of the Staffs property, for example:
public IEnumerable<SelectListItem> StaffOptions { get; set; }
Then:
model.StaffOptions = await _context.Staffs.AsNoTracking()
.Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() })
.ToListAsync();
In your view:
<select asp-for="StaffId" asp-items="#Model.StaffOptions" class="form-control"></select>
I have a layout page which has a partial view. The partial view needs to loop through a property on the view model to show a list of categories. When a category is displayed I need to show a list of documents in that category. /Home/Index works, but when I try to view /Documents/Category/{id}, I get an error:
Additional information: The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ViewModels.DocumentViewModel]', but this dictionary requires a model item of type 'ViewModels.HomeViewModel'.
_Layout.cshtml
...
<body>
#Html.Partial("_CategoryViewModel")
<div class="content">
#RenderBody()
</div>
HomeViewModel.cs
public class HomeViewModel {
...
public ICollection<DocumentCategory> Categories { get; set; }
public ICollection<Documents> Documents { get; set; }
...
}
_CategoryViewModel.cshtml (this should show a list of all categories)
#model ViewModels.HomeViewModel
...
#foreach (DocumentCategory item in Model.Categories)
{
<li>
<a href="#Url.Action("Category", "Documents", new { #id = #item.CategoryId })" title="View documents in the #item.Name category">
<span class="fa fa-files-o"></span> #item.Name
</a>
</li>
}
DocumentsController.cs
public ActionResult Category(int id)
{
var thisCategory = _ctx.Categories.Get(c => c.CategoryId == id).FirstOrDefault();
IEnumerable<DocumentViewModel> docs = null;
if(thisCategory == null)
{
TempData.Add("errorMessage", "Invalid category");
} else {
docs = thisCategory.Documents.ToList();
}
return View("Category", docs);
}
What's happening kind of makes sense - the PartialView on the Layout page needs to enumerate over a collection which isn't present in the ViewModel I'm using. I have no idea how to achieve this - the only way would seem to be to add a Categories property to every ViewModel in my site.
By default, using #Html.Partial() will pass the current model to the partial view, and because your Category.cshtml view uses #model List<DocumentViewModel>, then List<DocumentViewModel> is passed to a partial that expects HomeViewModel.
If you want to render a partial view for HomeViewModel on every page, then use #Html.Action() to call a ChildActionOnly method that returns the partial
[ChildActionOnly]
public ActionResult Categories
{
var model = new HomeViewModel()
{
.... // initialize properties
}
return PartialView("_CategoryViewModel", model)
}
and in the layout
#Html.Action("Categories", yourControllerName)
// or
#{ Html.RenderAction("Categories", yourControllerName); }
As I see it you have a few different alternatives.
1. Use Html.Action and create an Action that returns your view.
#Html.Action("Index", "Category") // Or your controller name.
I believe that there are some performance draw-backs with this approach because the whole MVC lifecycle will run again in order to render the result of the action. But then you can render the result of an action without having the correct model in the view that called it.
One may also argue that this breaks the MVC pattern, but it might be worth it.
2. Use a generic model (or an interface) in your _Layout.cshtml, and let your viewmodels inherit from that model.
In your _Layout.cshtml:
#model IBaseViewModel
And let all your viewmodels implement this interface.
public interface IBaseViewModel
{
ICollection<DocumentCategory> Categories { get; set; }
}
public interface IBaseViewModel<T> : IBaseViewModel
{
T ViewModel {get; set;}
}
Since you're placing #Html.Partial("_CategoryViewModel") in _Layout.cshtml I assume that it should be visible in all pages, so I think it's logical that all the controllers that are using _Layout.cshtml make sure that it gets the information it needs, and thus adding Categories to the model.
I use this approach all the time for stuff like breadcrumbs and menu-information (stuff that is used in all pages). Then I have a basecontroller that makes sure that Categories is populated with the correct info.
I have been trying to figure out how to populate a specfic interface via a form in a view. The interface is in a different project and namespace then that of my controller / view and is automatically generated for storing data in the database:
Interface namespace and Code:
DataAccess.DAL.IVehicle
namespace DataAccess.DAL
{
public partial interface IVehicle
{
String vehicleName { get; set; }
int maxSpeed { get; set; }
}
}
I have a controller which has an action method for receiving information from the form in the view:
Controller Code:
namespace coreproject.Controllers
{
public class NewVehicleController
{
[HttpPost, ValidateInput(false)]
public JsonResult AddVechicle(IVehicle newVehicle)
{
// I expect that newVechicle is populated via the form
}
}
}
I understand that I should be using Html.BeginForm in the view. Below is some code I came up with what I understand would be needed in the view.
View Code:
<%
// This is not working, I am not sure how to tell the view I want the form
// to use the interface located in the following namespace.
#Model DataAccess.DAL.IVehicle;
using (Html.BeginForm("AddVehicle", "NewVechicle", FormMethod.Post))
// Below I understand that I would need some code in the form of Html.EditorFor to
// populate the IVehicle interface in the form. I have seen this as an example:
<%: Html.EditorFor(model => model.VehicleName) %>
<%: Html.EditorFor(model => model.maxSpeed) %>
<%:
}
%>
The questions I have are twofold and are related to the view:
How do I tell the view I want to use an interface located in DataAccess.DAL, which resides in a different project and namespace than the view?
How do I populate the aforementioned interface in the form in order to pass it to the controller?
Any help would be greatly appreciated.
You are mixing a lot of concepts here.
Go to Visual Studio and create a new MVC website.
Run it and see how it works.
Then go on google and lookup the concept of interfaces.
Go back to your newly created MVC website and see the difference to what you have postet here.
Edit:
What you are trying is not possible!
You are asking the MVC framework to create an instance of an interface, this is not possible!
What you must do is to have a concrete class in the Action parameter:
[HttpGet]
public ActionResult AddVechicle()
{
return View(new Vehicle());
}
[HttpPost, ValidateInput(false)]
public JsonResult AddVechicle(Vehicle newVehicle)
{
// I expect that newVechicle is populated via the form
}
you could then declare the "Vehicle" class as follows
public class Vehicle :IVehicle
{
String vehicleName { get; set; }
int maxSpeed { get; set; }
}
I havent testet if the view will accept an interface as a model, you might better change it into the class "Vehicle"
<%
// view name: AddVehicle
// This is not working, I am not sure how to tell the view I want the form
// to use the interface located in the following namespace.
#Model Vehicle;
using (Html.BeginForm("AddVehicle", "NewVechicle", FormMethod.Post))
// Below I understand that I would need some code in the form of Html.EditorFor to
// populate the Vehicle concrete class in the form.
<%: Html.EditorFor(model => model.VehicleName) %>
<%: Html.EditorFor(model => model.maxSpeed) %>
<%:
}
%>
I'm using Entity Framework to create my data objects. Here's what my designer.cs file looks like:
namespace MyApp.WebUI.Models
{
...
[EdmEntityTypeAttribute(NamespaceName="MyAppDBModel", Name="AddressType")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class AddressType : EntityObject
{
...
}
...
}
I have a file called Validation.cs in which I want to keep all my validation for my entities. Here's what it looks like:
namespace MyApp.WebUI.Models
{
public class Validations
{
...
[MetadataType(typeof(AddressTypesValidation))]
public partial class AddressType
{
}
public class AddressTypesValidation
{
[Required(ErrorMessage = "Address Type name is required.")]
[StringLength(50, ErrorMessage = "Address Type name must be 50 characters or less.")]
public string Name { get; set; }
}
}
}
I have this in my view page:
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm("Edit", "AddressTypes", FormMethod.Post)) { %>
<div class="editor-label"><%: Html.LabelFor(m => m.Name) %></div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Name) %>
<%: Html.ValidationMessageFor(m => m.Name) %>
</div>
<input type="submit" value="Save" />
<% } %>
But my validations aren't loaded. If I try to submit the form with no value for Name, I get an error message saying The value '' is invalid. instead of my error message.
What am I doing wrong?
There is a fundamental flaw with your approach. It is generally understood that using your database objects as view models and having Mvc do its model binding on them is a very bad idea.
Darin has a great answer detailing the issues associated with using domain objects in views.
I think your issues are being caused because you're mix data objects with view models, and to quote Darin
About 60% of the question I am [Darin]
answering on StackOverflow in the
asp.net-mvc tag wouldn't have been
asked if the OP have used a view
model.
I'm not sure if you can have a partial class then have another partial class that is a nested class. Try having the partial you've declared not be a nested class.
Edit:
Just ran a quick test in VS (not the validation part) and you can't one part of a partial class in a nested type and have another part of the partial class as a non-nested type (or nested in a different type).
I may be off the mark here, but I believe that the Required attribute simply means 'not null', and your StringLengthValidator is only checking for an upper bound. It's not failing because you are sending through a string - unfortunately it's the String.Empty.
You're using the overload
[StringLength(int upperBound, [Parameters])]
Instead, try
[StringLength(int lowerBound, int upperBound, [Parameters])]
something like this, if you want minimum length of 1:
[Required(ErrorMessage = "Address Type name is required.")]
[StringLength(1, 50, ErrorMessage = "Address Type...")]
public string Name { get; set; }
I'm working on my first ASP.NET MVC application and have a strange issue. All the tutorials in regards to using strongly typed ViewData don't require casting/eval of ViewData / Model object but I get compilation errors if I don't cast to the ViewData object
ViewData class:
public class CategoryEditViewData
{
public Category category { get; set; }
}
Controller Action:
public ActionResult Edit(int id)
{
Category category = Category.findOneById(id);
CategoryEditViewData ViewData = new CategoryEditViewData();
ViewData.category = category;
return View("Edit", ViewData);
}
Works:
<%=Html.TextBox("name",
((Project.Controllers.CategoryEditViewData)Model).category.Name)) %>
Doesn't Work:
<%=Html.TextBox("name", Model.category.Name)) %>
Is there something that I am doing incorrectly - or do I have to cast to the object in the view all the time?
First, you should move the CategoryEditViewData class out of your controllers namespace, and into your models namespace. Create a new class under the Models folder to see what it should look like. It is good practice to put your models under the models folder.
Then your Control directive should look like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.CategoryEditViewData >" %>
Wait, just thought of something. In your view are you inheriting from you model????
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<namespace.Controllers.YourFormViewModel>" %>