How do I limit the columns that are returned by web api? - entity-framework

How do I limit the columns that are returned by web api & entity framework?
I would appreciate as much info as possible as I am still a newbie ;)
My Controller:
//GET: api/Creditors
public IQueryable<Creditor> GetCreditors()
{
return db.Creditors;
}
My Class:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace PurchaseOrders.Models
{
public class Creditor
{
[Key]
public int CreditorID { get; set; }
[MaxLength(10, ErrorMessage = "Maximum of 10 characters")]
public string CRKEY { get; set; }
[Display(Name = "Business Name")]
[MaxLength(40, ErrorMessage = "Maximum of 40 characters")]
public string BusinessName { get; set; }
[MaxLength(40, ErrorMessage = "Maximum of 40 characters")]
public string Address { get; set; }
[MaxLength(40, ErrorMessage = "Maximum of 40 characters")]
public string City { get; set; }
[MaxLength(4, ErrorMessage = "Maximum of 4 characters")]
public string State { get; set; }
[MaxLength(4, ErrorMessage = "Maximum of 4 characters")]
public string Postcode { get; set; }
[MaxLength(15, ErrorMessage = "Maximum of 15 characters")]
public string Phone { get; set; }
[MaxLength(15, ErrorMessage = "Maximum of 15 characters")]
public string Fax { get; set; }
[MaxLength(60, ErrorMessage = "Maximum of 60 characters")]
public string Email { get; set; }
[MaxLength(60, ErrorMessage = "Maximum of 60 characters")]
public string Website { get; set; }
[MaxLength(30, ErrorMessage = "Maximum of 30 characters")]
public string ContactName { get; set; }
[MaxLength(15, ErrorMessage = "Maximum of 15 characters")]
public string ABN { get; set; }
[Display(Name = "Registered for GST")]
public bool RegisteredForGST { get; set; }
}
}
This currently returns:
[{"CreditorID":1,"CRKEY":"test1","BusinessName":"test1","Address":"7 Smith Street","City":"Melbourne","State":"VIC","Postcode":"3000","Phone":null,"Fax":null,"Email":null,"Website":null,"ContactName":null,"ABN":"null","RegisteredForGST":true},{"CreditorID":2,"CRKEY":"test2","BusinessName":"test2","Address":"10 Smith Street","City":"SYDNEY","State":"NSW","Postcode":"2000","Phone":null,"Fax":null,"Email":null,"Website":null,"ContactName":null,"ABN":"null","RegisteredForGST":true}]
This is the result I want (only the "CreditorID" & "BusinessName"):
[{"CreditorID":1,"BusinessName":"test1"},{"CreditorID":2,"BusinessName":"test2"}]

In your question you're showing the json output of the query, so I assume you're making the GET request from Javascript. As you're using the IQueryable as the type of the return value from your API method, you should be able to take advantage of the OData support that WebApi provides so that you can issue an OData query to select just the columns you want. This this article for more detail on the OData support.
So firstly, the javascript side, assuming jQuery for ease of answering:
$.get('api/Creditors?$select=CreditorId,BusinessName', onSuccess)
The column names you want are specified in a comma separated list in the $select argument. (The onSuccess is a callback function you would define which would be passed the data that comes back from the API. See the jQuery documentation for more details.)
On the server side, you might need to derive your controller from ODataController instead of ApiController and you will need to add either the [Queryable] or the [EnableQuery] attribute to your GetCreditors() method depending on the version of WebApi you are using.
There is another bit of configuration you have to add if you find that you do need to inherit from ODataController to make this work, and that is to configure the OData endpoint. To do this you will need code similar to the following:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Creditor>("Creditors");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null, // or "api" ?
model: builder.GetEdmModel());
}
}
Somewhere in your web startup code (e.g. Application_Start) you will need to call this as follows:
GlobalConfiguration.Configure(WebApiConfig.Register);
Depending on how you've set your project up, some of this latter configuration might not be necessary as it will already be done, but I thought I'd mention it for good measure. Have a look at this page for more details.

This can be done using projection, here is sample using anonymous type:
db.Creditors
.Select(x => new {
x.CreditorID,
x.BusinessName,
})
.ToArray()
This will result in query to database which will get only two fields you need wrapped in anonymous class. You can return it directly from your WebAPI controller with JSON result.
If you need to pass result (which is of type SomeAnonymousClassICanNotReference[]) between layers, you can either use dymanic keyword (not a good option actually), or use your custom class like Select(x => new MyClass { Id = x.CreditorID ...

There are several different ways to handle this requirement without OData. I tend to use a projection query (as Lanorkin mentioned in his answer). Here are some examples.
1. Return a dynamic type from your WebAPI controller:
This is the quickest and easiest method. Some would argue that dynamic return types are sloppy, but they get the job done.
[HttpGet]
public dynamic GetCreditors() {
return db.Creditors
.Select(x => new {
x.CreditorID,
x.BusinessName,
}).ToArray()
}
2. Use an explicit return type in your controller:
This works with WebAPI as well as WCF which does not allow dynamic return types. This is a "better" approach if your standard practice is to use static return types.
Create a class for your return type:
public class CreditorResult {
public int CreditorID { get; set; }
public string BusinessName { get; set; }
}
Then your API method would look like this:
[HttpGet]
public CreditorResult[] GetCreditors() {
return db.Creditors
.Select(x => new CreditorResult() {
x.CreditorID,
x.BusinessName,
}).ToArray()
}
3. Use model attributes to control the output fields:
The current version of WebAPI uses JSON.NET as its serializer, and older versions can be set up to use it too. You can specify data attributes on your model to tell JSON.NET which properties to include or to ignore.
If you're using the code-first approach to Entity Framework then you can add the attributes directly to your class. If you're using the database-first approach (code generation) then it's safest to put your attributes in a Metadata class. http://www.ozkary.com/2015/01/add-data-annotations-to-entity.html
If you only want to include a few fields, you should add [DataContract] to the class and [DataMember] to the properties. Only the properties with [DataMember] will be included in the output. For example, the following would only return CreditorID and BusinessName:
[DataContract]
public class Creditor
{
[DataMember]
[Key]
public int CreditorID { get; set; }
[DataMember]
[Display(Name = "Business Name")]
[MaxLength(40, ErrorMessage = "Maximum of 40 characters")]
public string BusinessName { get; set; }
...
If you want to include most of your fields and ignore a few, the easier option is to add [JsonIgnore] to the properties that you wish to hide.
public class Creditor
{
[Key]
public int CreditorID { get; set; }
[JsonIgnore]
[MaxLength(10, ErrorMessage = "Maximum of 10 characters")]
public string CRKEY { get; set; }
[Display(Name = "Business Name")]
[MaxLength(40, ErrorMessage = "Maximum of 40 characters")]
public string BusinessName { get; set; }
...
There are a lot of other ways to fine-tune the output. Check out http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size for more details.

Related

Omit Id property in POST request to .Net Core Web Api

I have small .Net 5 Web Api project.
Code first approach is applied. MS SQL. EF Core.
The problem is - When controller receive model without Id - it fails with - "One or more validation errors occurred.". If controller received Id: 0 - it is Ok, It works.
The question is - Is it possible to omit Id property in POST request?
I want to completely omit Id from request.
Model:
public class Playback
{
public int Id { get; set; }
[Required]
public string Video_Name { get; set; }
[Required]
public string Video_Duration { get; set; }
public string Playback_User { get; set; }
public int Playback_Duration { get; set; }
}
Controller:
public async Task<IActionResult> AddPlaybackEvent2([FromForm] Playback model)
{
Playback #event;
try
{
#event = new Playback();
#event.Video_Name = model.Video_Name;
#event.Video_Duration = model.Video_Duration;
#event.Playback_User = model.Playback_User;
#event.Playback_Duration = model.Playback_Duration;
_db.Playbacks.Add(#event);
_db.SaveChanges();
}
catch (Exception e)
{
return BadRequest(e.Message);
}
return Ok(#event);
}
Checked in MS SQL - Primary key and identity increment for Id column is configured.
Adding this attribut to your Model. You need to set up this in your model if you using Code first approach
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }

How to manage DTO Implementaion for Rest-Api in .NET CORE? Alternatives?

I have a quite big query in my WebApi which filters data from different models to send them in a DTO Object to FrontEnd(Angular).
I think DTO could be the right approach because it isn't neccessary for the frontend to get all parameters from all models.
My problem consists in from mapping the DTO Object back to my WebApi Models.
I tried Automapper from NugetPackages but it didn't work. I also heard that AutoMapper isn't the right choice when projects are getting bigger and bigger.
Below is the Code for my DTO object, query and models:
public class ApplicationSettingsDto
{
public string KeyName { get; set; }
public string Wert { get; set; }
public string DefaultValue { get; set; }
public string Description { get; set; }
}
Models:
public partial class ApplicationSettings
{
public string KeyName { get; set; }
public string Wert { get; set; }
public int Typ { get; set; }
public string DisplayOrder { get; set; }
}
public partial class ApplicationSettingsDefaults
{
public Guid Id { get; set; }
public string KeyName { get; set; }
public string Value { get; set; }
public int ProduktOption { get; set; }
}
public partial class Text
{
public string KeyName { get; set; }
public string Sprache { get; set; }
public string Text1 { get; set; }
public DateTime LetzteAenderung { get; set; }
}
Query:
public IQueryable Description()
{
int produktOption = GetProduktOption();
var query = from appl in _repositoryContext.ApplicationSettings
from text in _repositoryContext.Text
from defaults in _repositoryContext.ApplicationSettingsDefaults
//filter DefaultValues
where appl.KeyName.Equals(defaults.KeyName) &&
(defaults.ProduktOption.Equals(produktOption) || defaults.ProduktOption.Equals(65535))
//Filter TextValues
where EF.Functions.Like(text.KeyName, "%" + appl.KeyName) ||
EF.Functions.Like(text.KeyName, "%" + appl.KeyName + "$Descr")
where EF.Functions.Like(text.Sprache, "de-DE")
select new ApplicationSettingsDto()
{
KeyName = appl.KeyName,
Wert = appl.Wert,
DefaultValue = defaults.Value,
Description = text.Text1
}
into output orderby output.KeyName select output;
return query;
}
So this question is not about an detailed implementation, it's only about recommendations for implementing DTO because mapping can be a pain in the *ss, like in my example.
I'm open to new ideas or patterns I don't know yet to try to manage problems like this.
Thanks in Advance ;)
This question is likely to be closed as you have working code, but my recommendation, after years of having tried AutoMapper, reflection based mappings, and hand-written mappings, that you should just stick with what is simplest and works.
You typically have to write the mapping logic for your DTOs once. The code you would write is legible and straightforward. When you move that to AutoMapper, you now end up having an often unrelated and less legible piece of code for something very, very simple.
In the event that you need the mapping logic in another function, extract it to a separate method. In the event that you need it in a separate class, promote that mapping function to a static method on your DTO.
Most of my mapping code looks like:
// Some controller code
da.GetStudents().Select(Map); // Map is the function below
In the controller, the following method is defined:
public StudentDto Map(Student student)
{
if (student == null) return null;
return new StudentDto
{
FirstName = student.FirstName,
...
};
}
Hope that helps.

WCF Data services - service operations not returning related entiities

I am working with EF 4.3 code first and using WCF Data services (5.0 just released) to expose the data over HTTP
I find that if I call a service operation from a browser I am able to get related entities back, but when I consume the service operation in a client app I am not getting back the related entities.
I have been researching this isuse it seems the EF enables lazy loading whn using the virtual key word when referencing a an ICollection, this some how prevents WCF data services from returing realted entities - is this true
If i browse locally and put a break point on my getUsersByName method I can see the related group entity but when it comes over the wire to a client app the gropup entity is missing.
Is there a configuration to enable this.
Thanks
eg
public partial class Group
{
public Group()
{
this.Users = new HashSet<User>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int GroupID { get; set; }
[Required(ErrorMessage = "Please enter a group name")]
[StringLength(50, ErrorMessage = "The group name is too long")]
public string GroupName { get; set; }
[Required]
public System.DateTime CreatedDate { get; set; }
[Required]
public bool Admin { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public partial class User
{
public User()
{
this.Groups = new HashSet<Group>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Required(ErrorMessage="Please enter a username")]
[StringLength(50,ErrorMessage="Username is too long")]
public string UserName { get; set; }
[Required(ErrorMessage="Please enter an email address")]
[RegularExpression(".+\\#.+\\..+",ErrorMessage="Please enter a valid email address")]
public string Email { get; set; }
[Required]
public System.DateTime CreatedDate { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public partial class TestContext : DbContext
{
public Test()
: base("name=TestEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Tell Code First to ignore PluralizingTableName convention
// If you keep this convention then the generated tables will have pluralized names.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
}
[ServiceBehavior(IncludeExceptionDetailInFaults=true)]
[JSONPSupportBehavior]
public class TestSVC : DataService<TestContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.SetServiceActionAccessRule("*", ServiceActionRights.Invoke);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
config.UseVerboseErrors = true;
}
[WebGet]
public User GetUserByName(string userName)
{
var user = (from u in this.CurrentDataSource.Users
where u.UserName == userName
select u).FirstOrDefault();
return user;
}
Actually regardless of what you do on the server side with the entity WCF DS will not return expanded entities by default. It's a feature. The reason is size of the message on the wire.
The client can request this behavior though (and that should work without making any modifications to the EF entities on the server side).
Let's assume you have a service operation GetProducts. You can issue a query like ~/GetProducts?$expand=Category which will include the Category entities in the result.
You also noted that the client doesn't get to see these, but you do see them in the browser. So are you already using the $expand? If that's the case the problem is solely on the client. Make sure that you request the results using the $expand on the client as well (depends on what code you're using, there's an Expand method in the LINQ on the client for this). And then you can use Fiddler to see if the client actually gets the results back the way you want. If that's the case and you still don't get the results in your client code, it might be due to MergeOptions. Try setting DataServiceContext.MergeOption to OverwriteChanges and try again (but be sure that you're aware of what this setting does).
Try removing the initialization of the navigation properties in the constructor because it will create problems with the proxy objects.
It's almost impossible to use lazy loading with serialization because when the serializer access the navigation properties the related entities will be loaded. This will lead to load whole database. So you need to disable lazy loading and use Include to load whatever you want or you can use some DTO with lazy loading enabled .

EF CF Mapping complex relationship with Fluent API

I am trying to create the following constraint in my model so that a Tag object's TagType is valid. A valid TagType is one whose OperatingCompanyId matches the Tag's Website's OperatingCompanyId. I realize that this seems convoluted however it makes sense from a business standpoint:
An Operating Company has WebSites. Websites contain Tags. Tags have a TagType(singular). TagTypes are the same across Operating Companies, meaning that if one Operating Company has twenty TagTypes and five WebSites, those twenty TagTypes should be able to be used across all fives of those WebSites. I want to ensure that a Tag's TagType cannot be one associated with another OperatingCompany.
What is the best way to create this constraint in the model? Do I need to change my POCO, or use the Fluent API?
Thanks in advance!
[Table("OperatingCompanies")]
public class OperatingCompany : ConfigObject
{
public OperatingCompany()
{
WebSites = new List<WebSite>();
}
[Required(ErrorMessage = "Name is a required field for an operating company.")]
[MaxLength(100, ErrorMessage = "Name cannot exceed 100 characters.")]
public string Name { get; set; }
public virtual ICollection<WebSite> WebSites { get; set; }
}
[Table("Websites")]
public class WebSite : ConfigObject
{
public WebSite()
{
WebObjects = new List<WebObject>();
}
[Required(ErrorMessage = "URL is a required field for a web site.")]
[MaxLength(100, ErrorMessage = "URL cannot exceed 100 characters for a web site.")]
[RegularExpression(#"\b(https?|ftp|file)://[-A-Za-z0-9+&##/%?=~_|!:,.;]*[-A-Za-z0-9+&##/%=~_|]", ErrorMessage = "The value entered is not a valid URL.")]
public string Url { get; set; }
public OperatingCompany OperatingCompany { get; set; }
[Required(ErrorMessage = "You must associate a web site with an operating company.")]
public Guid OperatingCompanyId { get; set; }
[InverseProperty("Website")]
public virtual ICollection<WebObject> WebObjects { get; set; }
}
[Table("Tags")]
public class Tag : ConfigObject
{
[Required(ErrorMessage = "Name is a required field for a tag.")]
[MaxLength(100, ErrorMessage = "Name cannot exceed 100 characters for a tag.")]
public string Name { get; set; }
public TagType TagType { get; set; }
[Required(ErrorMessage = "You must associate a tag with a tag type.")]
public Guid TagTypeId { get; set; }
public WebSite WebSite { get; set; }
[Required(ErrorMessage = "You must associate a tag with a web site.")]
public Guid WebSiteId { get; set; }
}
[Table("TagTypes")]
public class TagType : ConfigObject
{
[Required(ErrorMessage = "Name is a required field for a tag.")]
[MaxLength(100, ErrorMessage = "Name cannot exceed 100 characters for a tag type.")]
public string Name { get; set; }
public OperatingCompany OperatingCompany { get; set; }
[Required(ErrorMessage = "You must associate a tag type with an operating company.")]
public Guid OperatingCompanyId { get; set; }
}
One way to enforce this constraint is to take advantage of the new validation feature introduced as part of new DbContext API in EF 4.1. You can write a custom validation rule to make sure that tag types for any given company's website are selected from the valid tag types for that company. The following shows how it can be done:
public abstract class ConfigObject
{
public Guid Id { get; set; }
}
public class OperatingCompany : ConfigObject, IValidatableObject
{
public string Name { get; set; }
public virtual ICollection<WebSite> WebSites { get; set; }
public virtual List<TagType> TagTypes { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var allTagTypes = (from w in WebSites from t in w.Tags select t.TagType);
if (!allTagTypes.All(wtt => TagTypes.Exists(tt => tt.Id == wtt.Id)))
{
yield return new ValidationResult("One or more of the website's tag types don't belong to this company");
}
}
}
public class WebSite : ConfigObject
{
public string Url { get; set; }
public Guid OperatingCompanyId { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public OperatingCompany OperatingCompany { get; set; }
}
public class Tag : ConfigObject
{
public string Name { get; set; }
public Guid TagTypeId { get; set; }
public Guid WebSiteId { get; set; }
public TagType TagType { get; set; }
public WebSite WebSite { get; set; }
}
public class TagType : ConfigObject
{
public string Name { get; set; }
public Guid OperatingCompanyId { get; set; }
public OperatingCompany OperatingCompany { get; set; }
}
public class Context : DbContext
{
public DbSet<OperatingCompany> OperatingCompanies { get; set; }
public DbSet<WebSite> WebSites { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<TagType> TagTypes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>().HasRequired(t => t.WebSite)
.WithMany(w => w.Tags)
.HasForeignKey(t => t.WebSiteId)
.WillCascadeOnDelete(false);
}
}
As a result, EF will invoke that validate method each time you call DbContext.SaveChanges() to save an OperatingCompany object into database and EF will throw (and abort the transaction) if the method yields back any validation error. You can also proactively check for validation errors by calling the GetValidationErrors method on the DbContext class to retrieve a list of validation errors within the model objects you are working with.
It also worth noting that since you use your domain model as also a View Model for your MVC layer, MVC will recognize and honor this Validation rule and you can check for the validation result by looking into the ModelState in the controller. So it really get checked in two places, once in your presentation layer by MVC and once in the back end by EF.
Hope this helps.
however... if I understand the purpose of MVC / EF it is to have that
business logic inside of the Model...
And what model do you mean? If you take ASP.NET MVC and EF you will end with three areas which are sometimes called model:
EF model - that is set of classes with their mapping to database
Model-View-Controller - model here means something (usually business logic) consumed by your controller to prepare data for view
View model - In ASP.NET MVC view model is class with data exchanged between controller and view
If I look at your classes I see first and third model coupled together (most of the time this is considered as a bad practice). Your understanding is correct but mostly in terms of second model which is not represented by your classes. Not every "business logic" can be represented by mapping. Moreover it is not a point of data layer to do business logic.
Your mapping partially works (tag type is related only to one operating company) but still your data layer doesn't enforce all your business rules. Data layer still allows web site to have assigned tag with tag type from different operating company and your business logic must ensure that this will not happen. Avoiding this in database would be complicated because it would probably require complex primary keys and passing operating company Id to every dependent object.
If I were you, I will use business layer to filter Tagtype instead of do such constraint in database. For me that approach may be easier.

EF Code First: IValidatable Object not validating

I have an object in a simple test scenario that uses EF Code First and implements IValidatableObject. There's some very simple logic that adds a validation error and returns it back. There are also other validations on the object.
However, when saving the object - while the attribute based validations work - the IValidatableObject interface never seems to fire. Debugger doesn't step into it and the error never shows up with calling SaveChanges() or GetValidationErrors().
public class Customer : IValidatableObject {
[Key]
public int Id { get; set; }
[StringLength(50)]
[DisplayName("First Name")]
public string FirstName { get; set; }
[Required]
[DisplayName("Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[StringLength(100)]
public string Company { get; set; }
[StringLength(200)]
public string Email { get; set; }
[DisplayName("Credit Limit")]
public decimal CreditLimit { get; set; }
[DisplayName("Entered On")]
public DateTime? Entered { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public Customer()
{
Entered = DateTime.Now;
CreditLimit = 1000.00M;
Addresses = new List<Address>();
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
// add an error ALWAYS for testing - it doesn't show up
// and the debugger never hits this code
results.Add(new ValidationResult("Validate Added message",new [] { "Company" }));
return results;
}
When I now try to add a customer and check for validation errors:
public void AddNewCustomer()
{
Customer customer = new Customer();
context.Customers.Add(customer);
customer.LastName = "Strahl";
customer.FirstName = "Rick";
customer.Entered = DateTime.Now;
//customer.Company = "West Wind"; // missing causes val error
var errorEntries = context.GetValidationErrors();
}
I get ONE validation error for the company, but nothing from the IValidatableObject which should ALWAYS fail.
Any idea why?
Quote from Jeff Handley's Blog Post on Validation Objects and Properties with Validator:
When validating an object, the
following process is applied in
Validator.ValidateObject:
Validate property-level attributes
If any validators are invalid, abort validation returning the
failure(s)
Validate the object-level attributes
If any validators are invalid, abort validation returning the
failure(s)
If on the desktop framework and the object implements
IValidatableObject, then call its
Validate method and return any
failure(s)
This indicates that what you are attempting to do won't work out-of-the-box because the validation will abort at step #2.