I am in doubt about how to implement the Web API Routing when you have multiple DTOs for the same POCO.
Let us imagine the following scenario:
you have a POCO Class with. Let's say 100 properties
you need to display lists of that class in different platforms/UIs
i.e.
List 1 Prop A1 | Prop A2 | Prop A3 | Prop A4
List 2 Prop A21 | Prop A22 | Prop A23 | Prop A24 | Prop A25 | Prop A26 | Prop A27 | Prop A28 |
I will call it Company, since there are many examples using this class name
Is it correct to implement a Web API + DTOs strategy like this?
Classes are:
Company (the POCO class)
CompanyDetailDTO
CompanyLightListDTO
CompanyMediumListDTO
Example:
public class CompaniesController : ApiController
{
private MultiTierWebApiContext db = new MultiTierWebApiContext();
private static readonly Expression<Func<Company, CompanyDetailDTO>> AsCompanyDetailDTO =
x => new CompanyDetailDTO
{
Name = x.Name,
Email = x.Email,
isCustomer = x.isCustomer,
isSupplier = x.isSupplier,
Id = x.Id
};
private static readonly Expression<Func<Company, CompanyMediumListDTO>> AsCompanyMediumListDTO =
x => new CompanyMediumListDTO
{
Name = x.Name,
Email = x.Email,
Id = x.Id
};
private static readonly Expression<Func<Company, CompanyLightListDTO>> AsCompanyLightListDTO =
x => new CompanyLightListDTO
{
Name = x.Name,
Id = x.Id
};
// GET: api/Companies/LightList
[Route("api/Companies/LightList")] **It works, but is this a correct way to do it?**
[ResponseType(typeof(CompanyLightListDTO))]
public IQueryable<CompanyLightListDTO>GetCompaniesLightList()
{
return db.Companies.Select(AsCompanyLightListDTO); // default
}
// GET: api/Companies/MediumList
[Route("api/Companies/MediumList")]
[ResponseType(typeof(CompanyMediumListDTO))]
public IQueryable<CompanyMediumListDTO> GetCompaniesMediumList()
{
return db.Companies.Select(AsCompanyMediumListDTO); // default
}
// remaining code removed for simplicity
}
Thanks in advance for further help.
I would say you are on the right track with only providing the relevant information related to the particular DTO. Don't provide more data than necessary.
If you look at the following walk-through you will see how they follow a similar pattern to what you have in your example.
Create a REST API with Attribute Routing in ASP.NET Web API 2
Quoting for reference:
Instead, I want this request to return a subset of the fields. Also, I
want it to return the author's name, rather than the author ID. To
accomplish this, we'll modify the controller methods to return a data
transfer object (DTO) instead of the EF model. A DTO is an object that
is designed only to carry data.
// Typed lambda expression for Select() method.
private static readonly Expression<Func<Book, BookDto>> AsBookDto =
x => new BookDto
{
Title = x.Title,
Author = x.Author.Name,
Genre = x.Genre
};
// GET api/Books
public IQueryable<BookDto> GetBooks()
{
return db.Books.Include(b => b.Author).Select(AsBookDto);
}
My advice would be to separate the transformation/projection functionality out of the controller (SoC) into their own service(s) (SRP) and inject them (DI) into the ApiController. It will keep your Controllers light.
Related
I'm working on a dotnet mvc5 application. Here's a function from my api of customer controller
public IHttpActionResult GetCustomers()
{
var customerDtos = _context.Customers.ToList().Select(Mapper.Map<Customer, CustomerDto>);
return Ok(customerDtos);
}
I need to add "TYPEAHEAD" plugin to my application. The video series/instructor I'm following says to make the function code change to
public IHttpActionResult GetCustomers(string query = null)
{
var customersQuery = _context.Customers
.Include(c => c.MembershipType);
if (!String.IsNullOrWhiteSpace(query))
customersQuery = customersQuery.Where(c => c.Name.Contains(query));
var customerDtos = customersQuery
.ToList()
.Select(Mapper.Map<Customer, CustomerDto>);
return Ok(customerDtos);
}
in order to make "TypeAhead" plug in work on my view.
The only problem is previously while creating customers I didn't feel the need to add "MembershipType" class to my customer. So how do I use the new code without MembershipType. Is there any other attribute I can replace it with? Name, ID etc.
.Include(c => c.MembershipType);
essentially means that you also want to include the 'child' collection of MembershipType
See here for more information on Loading Related Entities
For your case, you can simply omit this.
public IHttpActionResult GetCustomers(string query = null)
{
var customersQuery = _context.Customers;
if (!String.IsNullOrWhiteSpace(query))
customersQuery = customersQuery.Where(c => c.Name.Contains(query));
var customerDtos = customersQuery
.ToList()
.Select(Mapper.Map<Customer, CustomerDto>);
return Ok(customerDtos);
}
You don't need to replace it with anything.
customersQuery is then an IQueryable<Customer> which the rest of this code can append Where clause to.
It is not executed against the database until the ToList call.
The project I am working is 'University Management System' and it's a big one. Right now, I am implementing the student registration section that works fine (A small portion of the project). I've used 'Three-Tier Architecture' and 'ORM - EF' in ASP.NET MVC template. In the project, I need to do some validations for registering students depending upon their year, department etc. So there are sections like DAL, BLL, finally controller and view. I've done the validations in the controller and getting the data from BLL that again retrieves data from DAL (This is the simple condition of 'Three-Tier Architecture'). So my questions are:
1) Is it OK to do the validations in the controller?
2) If not and need to do it in the BLL, will it be just fine and why or I can
continue doing it in the controller?
Note: To me, doing the validations in the controller or BLL seems OK and the same. Does it have any effect?
Right now, I've done the following:
DAL:
public List<Student> Add(int studentID, string studentName, string email, DateTime regDate)
{
List<Student> lst = null;
Student aStudent = new Student();
aStudent.StudentID = studentID;
aStudent.StudentName = studentName;
aStudent.Email = email;
aStudent.RegDate = regDate;
try
{
db.Students.Add(aStudent);
db.SaveChanges();
}
catch (Exception ex)
{
ex.ToString();
}
return lst;
}
BLL:
public List<Student> Add(int studentID, string studentName, string email, DateTime regDate)
{
return aStudentGateway.Add(studentID, studentName, email, regDate);
}
Controller:
/**Student Registration - Starts**/
[HttpPost]
public ActionResult AddStudent(Student aStudent)
{
List<Department> departments = aDepartmentManager.GetAllDepartments();
List<DepartmentViewModel> departmentsViewModel = aDepartmentManager.GetAllDepartmentViewModel();
DateTime yearInDateTime = Convert.ToDateTime(Request.Form["RegDate"]);
string extractYear = yearInDateTime.ToString();
var year = DateTime.Parse(extractYear).Year;
int department = Convert.ToInt32(Request.Form["Department"]);
List<Student> studentList = aStudentManager.GetAllStudents();
int count = 1;
var query = (from c in studentList
where c.Department == department && c.Year == year
select c).ToList();
foreach (var c in query)
{
if (query.Count() > 0)
{
int m = Convert.ToInt32(c.StudentID);
count = m + 1; //Incrementing the numbers by one with the table column
}
else
{
int m = 1;
count = m + 1; //Incrementing the numbers by one with the variable assigned one
}
}
Student student = new Student();
student.StudentName = Request.Form["StudentName"];
student.Email = Request.Form["Email"];
student.RegDate = Convert.ToDateTime(Request.Form["RegDate"]);
student.StudentID = count;
if (aStudentManager.ExistEmailAny(student.Email))
{
ViewBag.ErrorMessage = "Email already exists";
}
else
{
aStudentManager.Add(aStudent.StudentID, aStudent.StudentName, aStudent.Email, aStudent.RegDate);
ViewBag.Message = "Registration successful. See below to verify.";
/**This section used to show student details after registration**/
var result = (from c in departments
join d in departmentsViewModel on c.DepartmentID equals d.DepartmentId
where d.DepartmentId == department
select c);
foreach (var items in result)
{
if (count.ToString().Length > 1)
{
ViewBag.StudentID = items.Code + "-" + year + "-" + "0" + count;
}
else
{
ViewBag.StudentID = items.Code + "-" + year + "-" + "00" + count;
}
StudentViewModel.StudentID = student.StudentID;
StudentViewModel.StudentName = student.StudentName;
StudentViewModel.Email = student.Email;
StudentViewModel.RegDate = student.RegDate;
}
/**This section used to show student details after registration**/
}
return View();
}
/**Student Registration - Ends**/
I would provide multiple steps of validation in the different layers, depending on the context and the meaning of the layer.
First, it's a best practice to provide validation both on client and server side.
For the client side you should provide field checks for required fields and other simple validations. If you are using MVC you can use data annotations.
The same validation should be replicated in the controller. Here you should fail fast applying some kind of contract to the parameters that have been passed. One good practice is using Code Contracts that provide preconditions that need to be satisfied to go on in your pipeline of execution.
In the business layer provide the check that needs to be done in the business logic.
Finally in the data access layer provide all the checks that are needed to persist your data. If you are using EF a good practice is implementing the IValidatableObject for your entity classes. Here in Scott Gu's blog you can find a post that explains this technique.
Even though this approach look like it will introduce repetitions, it will provide consistency in your data and separate concerns between your layers.
1) Is it OK to do the validations in the controller?
No at all, it would be more better to use Data Annotation Validator Attributes, and to do validation in your model class.
Second thing, you're doing some stuff of DAL in your controller, like
List<Department> departments = aDepartmentManager.GetAllDepartments();
List<DepartmentViewModel> departmentsViewModel = aDepartmentManager.GetAllDepartmentViewModel();
var query = (from c in studentList
where c.Department == department && c.Year == year
select c).ToList();
These all queries should be in DAL, which is exact use of DAL to interact with the database, and keep your controller clean.
Third thing,
If you pass Student to the controller, then not need to get each attribute using Request.Form.
Hope this make sense!
Is it possible to generate different databases according to a specific parameter?
My final goal is john.domain.com => create john db, paul.domain.com => create paul db
How could I achieve this using EF6 code first, MVC5? Could model first do it?
Yes you can change the connection string at runtime, something like.
Need to add reference to System.Data.
public static class ConnectionStringExtension
{
public static void ChangeDatabaseTo(this DbContext db, string newDatabaseName)
{
var conStr = db.Database.Connection.ConnectionString;
var pattern = "Initial Catalog *= *([^;]*) *";
var newConStr = Regex.Replace(conStr, pattern, m =>
{
return m.Groups.Count == 2
? string.Format("Initial Catalog={0}", newDatabaseName)
: m.ToString();
});
db.Database.Connection.ConnectionString = newConStr;
}
}
Usage.
using (var db = new AppContext())
{
// Uses it just before any other execution.
db.ChangeDatabaseTo("MyNewDatabase");
}
I am trying to create a conditional ContractResolver so that I can control the serialization differently depending on the web request/controller action.
For example in my User Controller I want to serialize all properties of my User but some of the related objects I might only serialize the primitive types. But if I went to my company controller I want to serialize all the properties of the company but maybe only the primitive ones of the user (because of this I don't want to use dataannotations or shouldserialize functions.
So looking at the custom ContractResolver page i created my own.
http://james.newtonking.com/projects/json/help/index.html?topic=html/ContractResolver.htm
It looks like this
public class IgnoreListContractResolver : DefaultContractResolver
{
private readonly Dictionary<string, List<string>> IgnoreList;
public IgnoreListContractResolver(Dictionary<string, List<string>> i)
{
IgnoreList = i;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
if(IgnoreList.ContainsKey(type.Name))
{
properties.RemoveAll(x => IgnoreList[type.Name].Contains(x.PropertyName));
}
return properties;
}
}
And then in my web api controller action for GetUsers i do this
public dynamic GetUsers()
{
List<User> Users = db.Users.ToList();
List<string> RoleList = new List<string>();
RoleList.Add("UsersInRole");
List<string> CompanyList = new List<string>();
CompanyList.Add("CompanyAccesses");
CompanyList.Add("ArchivedMemberships");
CompanyList.Add("AddCodes");
Dictionary<string, List<string>> IgnoreList = new Dictionary<string, List<string>>();
IgnoreList.Add("Role", RoleList);
IgnoreList.Add("Company", CompanyList);
GlobalConfiguration
.Configuration
.Formatters.JsonFormatter
.SerializerSettings
.ContractResolver = new IgnoreListContractResolver(IgnoreList);
return new { List = Users, Status = "Success" };
}
So when debugging this I see my contract resolver run and it returns the correct properties but the Json returned to the browser still contains entries for the properties I removed from the list.
Any ideas what I am missing or how I can step into the Json serialization step in webapi controllers.
*UPDATE**
I should add that this is in an MVC4 project that has both MVC controllers and webapi controllers. The User, Company, and Role objects are objects (created by code first) that get loaded from EF5. The controller in question is a web api controller. Not sure why this matters but I tried this in a clean WebApi project (and without EF5) instead of an MVC project and it worked as expected. Does that help identify where the problem might be?
Thanks
*UPDATE 2**
In the same MVC4 project I created an extension method for the Object class which is called ToJson. It uses Newtonsoft.Json.JsonSerializer to serialize my entities. Its this simple.
public static string ToJson(this object o, Dictionary<string, List<string>> IgnoreList)
{
JsonSerializer js = JsonSerializer.Create(new Newtonsoft.Json.JsonSerializerSettings()
{
Formatting = Formatting.Indented,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
ContractResolver = new IgnoreListContractResolver(IgnoreList),
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
js.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
var jw = new StringWriter();
js.Serialize(jw, o);
return jw.ToString();
}
And then in an MVC action i create a json string like this.
model.jsonUserList = db.Users.ToList().ToJson(IgnoreList);
Where the ignore list is created exactly like my previous post. Again I see the contract resolver run and correctly limit the properties list but the output json string still contains everything (including the properties I removed from the list). Does this help? I must be doing something wrong and now it seems like it isn't the MVC or web api framework. Could this have anything to do with EF interactions/ proxies /etc. Any ideas would be much appreciated.
Thanks
*UPDATE 3***
Process of elimination and a little more thorough debugging made me realize that EF 5 dynamic proxies were messing up my serialization and ContractResolver check for the type name match. So here is my updated IgnoreListContractResolver. At this point I am just looking for opinions on better ways or if I am doing something terrible. I know this is jumping through a lot of hoops just to use my EF objects directly instead of DTOs but in the end I am finding this solution is really flexible.
public class IgnoreListContractResolver : CamelCasePropertyNamesContractResolver
{
private readonly Dictionary<string, List<string>> IgnoreList;
public IgnoreListContractResolver(Dictionary<string, List<string>> i)
{
IgnoreList = i;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
string typename = type.Name;
if(type.FullName.Contains("System.Data.Entity.DynamicProxies.")) {
typename = type.FullName.Replace("System.Data.Entity.DynamicProxies.", "");
typename = typename.Remove(typename.IndexOf('_'));
}
if (IgnoreList.ContainsKey(typename))
{
//remove anything in the ignore list and ignore case because we are using camel case for json
properties.RemoveAll(x => IgnoreList[typename].Contains(x.PropertyName, StringComparer.CurrentCultureIgnoreCase));
}
return properties;
}
}
I think it might help if you used Type instead of string for the ignore list's key type. So you can avoid naming issues (multiple types with the same name in different namespaces) and you can make use of inheritance. I'm not familiar with EF5 and the proxies, but I guess that the proxy classes derive from your entity classes. So you can check Type.IsAssignableFrom() instead of just checking whether typename is a key in the ignore list.
private readonly Dictionary<Type, List<string>> IgnoreList;
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
// look for the first dictionary entry whose key is a superclass of "type"
Type key = IgnoreList.Keys.FirstOrDefault(k => k.IsAssignableFrom(type));
if (key != null)
{
//remove anything in the ignore list and ignore case because we are using camel case for json
properties.RemoveAll(x => IgnoreList[key].Contains(x.PropertyName, StringComparer.CurrentCultureIgnoreCase));
}
return properties;
}
Then the ignore list must be created like this (I also used the short syntax for creating the list and dictionary):
var CompanyList = new List<string> {
"CompanyAccesses",
"ArchivedMemberships",
"AddCodes"
};
var IgnoreList = new Dictionary<Type, List<string>> {
// I just replaced "Company" with typeof(Company) here:
{ typeof(Company), CompanyList }
};
Be aware that, if you use my code above, adding typeof(object) as the first key to the ignore list will cause this entry to be matched every time, and none of your other entries will ever be used! This happens because a variable of type object is assignable from every other type.
I have a Controller which returns a ViewModel to a View and it works just fine. I want to migrate to a Repository pattern but am having trouble getting the correct syntax in the repository. I have created the repository and the interface to it.
public interface IShippingRepository
{
IQueryable<ShippingCommHdr> All { get; }
IQueryable<ShippingCommHdr> AllIncluding(params Expression<Func<ShippingCommHdr, object>>[] includeProperties);
void InsertOrUpdate(ShippingCommHdr shippingcommhdr);
void Delete(int id);
void Save();
}
Here is the code form my Controller that I want to move to the repository:
public ViewResult ShippingSummary()
{
CPLinkEntities context = new CPLinkEntities();
var shipments =
from h in context.ShippingCommHdrs
where (h.CompletedDate == null)
join
e in context.vHr_Employees on h.CreatedBy equals e.ID
join
s in context.Shippers on h.ShipperID equals s.ShipperID
join
r in context.vAaiomsSites on h.ShipToSiteID equals r.SiteID
join
c in context.vHr_Employees on h.CreatedBy equals c.ID
join
p in context.vHr_Employees on h.FromSitePOC equals p.ID
select new
{
h.ID,
ShippedToSite = r.SiteName,
h.DateShipped,
h.EstDeliveryDate,
h.TrackingNo,
h.HeaderComments,
h.ShippingCommLI.Count,
s.Shipper,
CreatedBy = c.LastName,
FromSitePoc = p.LastName
};
var model = new List<ShippingSummaryVM>();
foreach (var h in shipments)
{
var viewModel = new ShippingSummaryVM
{
ID = h.ID,
ShippedToSite = h.ShippedToSite,
DateShipped = h.DateShipped,
EstDeliveryDate = h.EstDeliveryDate,
TrackingNo = h.TrackingNo,
FromSitePOC = h.FromSitePoc,
Shipper = h.Shipper,
HeaderComments = h.HeaderComments,
NumOrders = h.Count,
CreatedBy = h.CreatedBy,
};
model.Add(viewModel);
}
return View(model);
}
If I could get this one Controller/Repository to work, I can then migrate all the others over fairly quickly. thanks for any assistance
I'd start by adding a method definition to the repository interface for the query you need to execute. The repository can give this query a meaningful name:
public interface IShippingRepository
{
IQueryable<Shipment> GetShipments()
// ...
}
In the controller you'll need an instance of the repository. You can inject it into a constructor, or create one in a constructor, but either way the repository will need to talk to the CPLinkEntities context behind the scenes. You'll need to pass a context into the repository for the repository to work with.
public class SomeController : Controller
{
IShippingRepository _shippingRepository;
public SomeController()
{
_shippingRepository = new ShippingRepository(new CPLinkEntities());
}
public ViewResult ShippingSummary()
{
var shipments = _shippingRepository.GetShipments();
// ....
}
}
A concrete repository definition might look like the following.
public class ShippingRepository : IShippingRepository
{
CPLinkEntities _entities;
ShippingRepository (CPLinkEntities entities)
{
_entites = entities;
}
public IQueryable<Shipment> GetShipments()
{
return from ship in _entities.Ships join ... join ... select
}
}
Your controller method basically has 2 responsibilities
Run a Query
Map the results of the query into a view model
You can put that query into a repository, and then you could use an auto-mapper tool like AutoMapper or ValueInjecter to help you map the results of your query to a view model.
Your resulting controller method would simply call the repository to get a list of CPLinkEntities. Your controller method could then take those entities and then call the automapper to give you a list of ShippingSummaryVM's. I've left some implementation details, but this should give you a high level understanding of how to achieve what you are asking.
Option A: Have a stronger domain model. Your repository would responsible for loading root level domain objects and you let the underlying OR/M handle object traversal. Your controller would call a method on shipment to find shipments that are not yet completed. You'd get back a shipment object and could traverse to the related entities to get site name and other details you need for your VM
Option B: Have repositories that return all for each entity, and then do the join in a business or service layer. Entity Framework won't load all even if you say ShippingRepository.All. It only loads at the last responsible moment (when you need a materialized result). So you could have a business method that joins the "All" on each entity and filters based on completed date then returns back the result.
Option A is better but might be a lot more work.