Web API OData V3 get entity by string (email) - asp.net-web-api

I'm creating some Web API's for internal use and one of the 3rd party applications that will interact with it can only use odata v3 (i.e. not odata v4).
I'm trying to query another 3rd party application from this Web API - get a contact by email but not sure how to go about getting it by a string and not id.
I have a simplified model like so:
public class Contact
{
[Key]
public int Id { get; set; }
public string Email { get; set; }
public string CompanyName { get; set; }
}
Simplified controller:
public IHttpActionResult GetContact([FromODataUri] int key)
{
var contact = _repository.GetContact(key);
// return..
}
Simplified WebApiConfig
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
var container = new UnityContainer();
container.RegisterType<IContactRepository, ContactRepository>(new ContainerControlledLifetimeManager());
config.DependencyResolver = new UnityResolver(container);
builder.EntitySet<Contact>("Contacts");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
If I look at the metadata document it shows Id as expected:
<EntityType Name="Contact">
<Key>
<PropertyRef Name="Id"/>
</Key>
In my model if I move the [Key] annotation to email and then in WebApiConfig
change builder.EntitySet<Contact>("Contacts");
to
builder.EntitySet<Contact>("Contacts").EntityType.HasKey(c => c.Email);
I also change the key input parameter in the controller from int to string
it makes a composite key of Id and Email
<EntityType Name="Contact">
<Key>
<PropertyRef Name="Email"/>
<PropertyRef Name="Id"/>
</Key>
http://xxx:52759/odata/Contacts('some#email.com') works if I use fiddler to test but my 3rd party application reads the metadata and require me to pass in Id and Email, I don't have the Id at that time.
If I rename Id to something like cId in my model it's not part of the key in the metadata anymore but I don't think this is the right way to go about it?
<EntityType Name="Contact">
<Key>
<PropertyRef Name="Email"/>
</Key>
Am I on track or do I have to do something completely different if I want to get a contact by email (and in that way get id with the response back)?
Thanks in advance.

You should pass int Id and Email is it is a composite key, in your scenario, I think your mean AlternateKey, which is suported in V4 https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataAlternateKeySamples

Related

Using a viewmodel which ignores the properties from the model

I'm using entity framework and MVC (both CORE if that matters) to make a site. Using the model and tying it directly to the view is fine all the CRUD actions work, everything is lovely.
I wanted to use a couple of pages to access the model so the site looked better, so split the controls out onto separate views and added a corresponding viewmodel for each, so my project looks like this
-Model
--CustomerModel
-ViewModel
--CustomerNameVM
--CustomerAddressVM
-View
--CustomerNameView
--CustomerAddressView
The CustomerModel has a number of properties
Forename
Surname
Address
Postcode
with Forename and Surname in the CustomerNameVM and Address and Postcode in CustomerAddressVM. Surname is defined as [Required] in the model but not in CustomerNameVM which I believe is the correct way to do it.
I'm struggling to get the model loaded into the viewmodel and then trying to save it when I'm editing the address details in CustomerAddressView because it errors when I try and save as the viewmodel doesn't contain Surname (from the model), so it's null and therefore the [Required] criteria isn't being met.
I've tried a few methods of trying to get past this like :-
Jeffrey Palermo's Onion Architecture
Repositories
domain models
amongst others which all end up with the same problem, I can't save the Address as the Surname is null.
How do I ignore validation criteria for the properties of the model that aren't being referenced in the viewmodel?
Or how do I load and reference only those properties of the model that are present in viewmodel?
Edit
For those who keep asking for code, which codeset? I've tried 30 of them now, none of which do the job. Which one of these do you want? I'm trying to get a general idea of how this is supposed to work as none of the methods, documentation and associated examples function.
Here's a starter for 10, it's unlike the other 29 codesets but it's code and probably the shortest.
The controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Step2Address(int? id, [Bind("CustomerID,txtAddress,txtPostcode")] VMAddress VMAddress) {
if (ModelState.IsValid) {
//the saving code
}
return View(VMAddress);
}
the model
public class clsCustomer {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomerID { get; set; }
public string Forename { get; set; }
[Required]
public string Surname { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
the viewmodel
public class VMAddress {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomerID { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
}
the view
#model theProject.Models.VMStep2Contact
<form asp-action="Step2Address">
<input type="hidden" asp-for="ComplaintID" />
<input asp-for="txtAddress"/>
<input asp-for="txtPostcode"/>
<input type="submit" value="Save" />
the context
public class ContextCustomer : DbContext {
public ContextCustomer(DbContextOptions<ContextCustomer> options) : base(options) {
}
public DbSet<clsCustomer> Customer{ get; set; }
}
Clicking "Save" on the webpage calls the controller straight away, which hits the first line if (ModelState.IsValid) and as the Surname isn't set and is [Required] the ModelState is not valid so no save is attempted.
I don't actually understand what the problem is, and without code, it's impossible to say what you might be doing wrong. Generally speaking, you shouldn't have any issues since you're using view models.
A view model is, of course, not saved directly to the database, so it has to be mapped over to an actual entity class that you will be saving. In the case of an edit, you should retrieve all relevant entities from the database, map any posted values onto the appropriate properties on those entities, and then save those entities back to the database. If you're doing this, presumably, the customer model should already contain the Surname and other required properties, and you'd only be modifying/setting the address properties.
If you're doing a create, then, simply you can't just take address information. You need the name as well, so for this, you'd need to pass a view model that contains at least all required fields, such that you have all the information you need to actually save the entity.
If you're trying to do a multi-step form, where you collect all the information over multiple posts, then you simply must persist the posted data somewhere other than the database until you have enough of it to actually save an entity to the database. This is usually accomplished via Session/TempData.

Serialization attributes on TableEntity in Azure Table Storage

Im using web API to return data in azure table storage. Im returning a class that I 'm inheriting TableEntity in a class and adding properties but want to keep to the .Net convention of capitalized property names but also keep to the JavaScript/json convention of lowercase properties names.
I've tried adding the Json.net property attributes to the class but it appears to be ignored. E.g.:
[JsonProperty("id")]
public string ID {get;set;}
If the instance has a value set on ID, null is represent in the serialized result.
According to your description, I tested this issue on my side and found it works well on my side and Azure. Here is my detailed steps, you could refer to it.
Create a controller named UserInfoController in the Web API application with the Get function like this:
// GET: api/UserInfo
[HttpGet]
public async Task<string> Get()
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(<your-Storage-ConnectionString>);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable cloudTable = tableClient.GetTableReference("UserInfo");
TableQuery<User> query = new TableQuery<User>()
.Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Grade Four"));
var results =await cloudTable.ExecuteQuerySegmentedAsync(query, null);
//Serialize the object to string by using the latest stable version of Newtonsoft.Json
string jsonString = JsonConvert.SerializeObject(results);
return jsonString;
}
Entity
public class User : TableEntity
{
public User(string partitionKey, string rowKey)
{
this.PartitionKey = partitionKey;
this.RowKey = rowKey;
}
public User() { }
[JsonProperty("id")]
public long ID { get; set; }
[JsonProperty("username")]
public string UserName { get; set; }
[JsonProperty("phone")]
public string Phone { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
}
Result
Deploy the Web API application to Azure, then you could find the following result by calling the function via Fiddler.
In summary, please try to check the version of Json.NET you are using. If you aren't using the latest (9.0.1), then please try to upgrade to the latest version and run your application again to find whether it could work as expected.
FYI - while this doesn't answer the direct answer of how to get TableEntity to respect JSON.net attributes... I was able to solve the use case by overriding the ReadEntity and WriteEntity method in the inherited class:
e.g.
public class User : TableEntity{
//Upper case name
public string Name {get; set};
public override void ReadEntity(IDictionary<string, AzureTableStorage.EntityProperty> properties, OperationContext operationContext){
base.ReadEntity(properties, operationContext);
//lower case
this.Name = properties["name"];
}

How to change the primary key for a EntityData derived class default string Id property to an int property?

I recently started to learn about Azure Mobile Services, I followed this tutorial about it and the classes for my model are required to inherit from the EntityData class.
From the EntityData source code, an Id property is already defined to act as a primary key, but it is defined as string which doesn't work for my Model that uses int.
My class looks like this:
public partial class Role : EntityData
{
public Role()
{
this.Users = new HashSet<User>();
}
[Key]
public int RoleId { get; set; }
public string Title { get; set; }
public virtual ICollection<User> Users { get; set; }
}
If I try to use this class, I get an error message saying an Id property is already defined.
Is there a way to define a different property as a primary key? In case this change is not possible, is there a way to use this string Id property as an incremental primary key?
The best solution is to use automapper. Here's a blog post that outlines how to do it, essentially you store an int, but transform it into a string when it is sent over the wire:
http://blogs.msdn.com/b/azuremobile/archive/2014/05/22/tables-with-integer-keys-and-the-net-backend.aspx

MVC3 + Simple Membership: Accessing User Profiles Through Entity Framework

I'm using the SimpleMembership.MVC3 package with my MVC3 application and I want to be able to access users from the table through Entity Framework
In examples for doing this with MVC4, you can simply create a POCO to mirror the User table that's been generated, add your DbSet in your DbContext implementation and then query the DbSet like you normally would, ie: context.Users.
This collection is always returning 0 items for me even though there are rows in the table. What am I doing wrong? Here's what I got so far:
[Table("User")]
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class TestContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
}
In my service:
model.Accounts = context.Users.ToList();
Thanks!
You do not create your a POCO that mirrors the User table in order to access it directly from EF. There is already a POCO created by the Internet template when you created the project, which you can customize as described here. This same article also shows how you can access the user information by accessing EF directly. You do not create your own context, there is one already in place that you use. Here is a code snippet from that article.
var context = new UsersContext();
var username = User.Identity.Name;
var user = context.UserProfiles.SingleOrDefault(u => u.UserName == username);
var email = user.Email;
The article also has links to download the source code that demonstrates the details on how to do this.
I circumvented the membership classes entirely and implemented a pure EF membership system. I leveraged the System.Web.Helpers Crypto helpers to handle password hashing and just create the AuthCookie when needed.

Missing inverse property in asp.net webapi odata $metadata

have very simple relationship between two entities and I am trying to expose them with asp.net webapi odata controllers but it seems that something is wrong with $metadata.
When I run jaydatasvcutil.exe on the $metadata I get warning: inverseProperty other side missing.
When I use breezejs loadNavigationProperty I get similar error.
I have the problem even with official example.
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/working-with-entity-relations
You can observe the $metadata here http://sdrv.ms/Z5Klfw
Please help.
When we are generating navigation properties we don't reuse the relationships.
For example, lets say you have simple model,
public class Product
{
public int Id { get; set; }
public Supplier Supplier { get; set; }
}
public class Supplier
{
public int Id { get; set; }
public Product[] Products { get; set; }
}
The $metadata for the navigation properties that we generate looks like this,
<NavigationProperty Name="Supplier" Relationship="ProductsService.Models.ProductsService_Models_Product_Supplier_ProductsService_Models_Supplier_SupplierPartner" ToRole="Supplier" FromRole="SupplierPartner" />
<NavigationProperty Name="Products" Relationship="ProductsService.Models.ProductsService_Models_Supplier_Products_ProductsService_Models_Product_ProductsPartner" ToRole="Products" FromRole="ProductsPartner" />
Notice that we are generating two relationships instead of one. The reason we do that is that it is a hard problem to figure out if two navigation properties represent the same relationship. Take the instance of Product and Manufacturer.
public class Manufacturer
{
public int Id { get; set; }
public Product[] RawMaterials { get; set; }
public Product[] Produces { get; set; }
}
public class Product
{
public int Id { get; set; }
public Manufacturer[] Producers { get; set; }
public Manufacturer[] Consumers { get; set; }
}
It is not trivial to figure out that Maufacturer.RawMaterials and Product.Consumers should share the same relationship and Manufaturer.Produces and Product.Producers should share the same relationship. We chose not to do it because the clients that we know of don't make much out of this information.
All this happens because OData uses the same EDM model as the entityframework. Entityframework requires this information as it maps these relationships to association sets which would become tables in the database.
Another reason we chose not to do it is that this could be going away in OData V4. Check out the working draft here (page 23 and page 57 would be of interest). In short, navigation properties in $metadata in OData V4 would look more like this,
<NavigationProperty Name="Category" Type="Self.Category" Nullable="false" Partner="Products" />
Notice that there is no relationship and there would be no association sets.

Resources