CosmosDb not using the ContractResolver provided when generating select queries - azure-cosmosdb

I have a project where I'm using CosmosDb (SQL API) as my database. It's a .Net Core project and I'm using the latest stable NuGet packages.
The document client is created as follows and use a custom contract resolver.
new DocumentClient(new Uri(settings.DatabaseUri), settings.DatabaseKey,
new JsonSerializerSettings
{
ContractResolver = new PrivateSetterCamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter>
{
new EmailJsonConverter()
}
});
I have a collection called EmailAccount
public class EmailAccount : Entity
{
public string Email { get; private set; }
public string DisplayName { get; private set; }
public EmailAccount(DDD.Core.ValueObjects.Email email,
string displayName)
{
Email = email ?? throw new ArgumentNullException(nameof(email));
DisplayName = string.IsNullOrWhiteSpace(displayName) ? throw new ArgumentNullException(nameof(displayName)) : displayName;
}
}
All the properties are converted into camel-case when serialized which all works fine. But the problem is when I try to filter the documents. The SQL query that's generated looks something like this when I try to filter by the Email.
SELECT * FROM root WHERE (root["Email"] = "amila#iagto.com")
The problem is with the case of the property (Email). The property in the database is email but the query generator doesn't seem to be adhering to the ContractResolver provided and generates the above sql query which doesn't return any result.
If I put [JsonProperty("email")] above the Email property, the query is generated properly. Anyway to get the query generated properly without using attributes in the Entity class?
Any help much appreciated.

You need to set the JsonSerializerSettings at the CreateDocumentQuery level for the LINQ to SQL to pick it up.
This property was added in the SDK on 2.0.0+ versions.

Related

Need help understanding API and LINQ

I'm trying to set up an API for a system I'm working on, but the LINQ seems to not grab the parameters.
A bit of background: During covid I've been working with a local business owner to develop an info system for his business. So far, everything has been kept in the browser, but now we want to create a small windows form application the users can download instead of using the browser. The application will be much smaller in scope than the full site, but I don't want the SQL connection in the form.
So I guess my first question is, am I being overly cautious at not wanting the SQL connector in the client and wanting them to connect to the database, via an API, or is it safe enough to add the connection and calls directly in the application (I know how to do this, it's the API part I can't figure out). I'm thinking about it from a security point of view - would the users be able to find the connection and potentially do harm to my database by having it straight in the application or is it safe there?
If using API calls is the proper way to go, that leads me to my second question. How do I configure it properly?
This is my table (I've been following the Microsoft ToDoItems tutorials):
CREATE TABLE [dbo].[TodoItems] (
[Id] [int] identity(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[IsComplete] [bit] NULL,
[Secret] [nvarchar](10) NULL
) ON [PRIMARY]
GO
My test form has a single button, which when pressed calls this method:
static async Task RunAsync()
{
// Update port # in the following line.
client.BaseAddress = new Uri("https://localhost:7217/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
try
{
// Create a new product
TodoItem product = new TodoItem
{
Name = "Gizmo",
IsComplete = false,
Secret = "false"
};
var url = await CreateProductAsync(product);
Console.WriteLine($"Created at {url}");
// Get the product
product = await GetProductAsync(url.PathAndQuery);
ShowProduct(product);
// Update the product
Console.WriteLine("Updating IsCompleted...");
product.IsComplete = true;
await UpdateProductAsync(product);
// Get the updated product
product = await GetProductAsync(url.PathAndQuery);
ShowProduct(product);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
My ToDoItem class looks like this:
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
public string Secret { get; set; }
}
My first issue is creating the ToDoItem. This method should do the trick:
static async Task<Uri> CreateProductAsync(TodoItem product)
{
HttpResponseMessage response = await client.PostAsJsonAsync(
"api/todoitems", product);
response.EnsureSuccessStatusCode();
// return URI of the created resource.
return response.Headers.Location;
}
However, when I run the method my API logs this error and nothing is posted to the database:
Executed DbCommand (46ms) [Parameters=[#p0='?' (DbType = Boolean), #p1='?' (Size = 4000), #p2='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [TodoItems] ([IsComplete], [Name], [Secret])
VALUES (#p0, #p1, #p2);
SELECT [Id]
FROM [TodoItems]
WHERE ##ROWCOUNT = 1 AND [Id] = scope_identity();
The way I read this, and I might be wrong, the method CreateProductAsync (which gets a product with the values "Gizmo", false and "false") simply doesn't transfer the values to the API.
For reference, my API ToDoContext class look like this:
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; } = null!;
}
Do I need to add something to this class? I'm wholly unfamiliar with both API and LINQ, but I did figure out that changing the table name to ToDoItems made the connection for me on its own.

OData .NET Core API - Conditional AutoExpand ($expand)

I am developing a .NET Core Web API with OData capabilities.
For some entities, I would like to $expand some properties provided the user has the appropriate permissions.
Therefore, I need to handle this $expand part in the API, without the user having to specifically ask for the nested entities he has access to.
Is there a way to apply the AutoExpand attribute conditionally?
Thank you for your help!
EDIT (Possible solution)
I created a new class, inherited from EnableQueryAttribute, then overrode public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions).
In this method, I forge a new query string based on the permissions:
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
// Keep the original query string
string originalQueryString = queryOptions.Request.QueryString.Value;
// Suppose you can get the permissions as followed
var lPermissions = this.GetPermissions();
// Then create a query string based on it
string forgedQueryString = this.GenerateQueryString(lPermissions);
// Forge a new ODataQueryOptions
queryOptions.Request.QueryString = new QueryString(forgedQueryString);
var oQueryOptions = new ODataQueryOptions(queryOptions.Context, queryOptions.Request);
var queryEntities = oQueryOptions.ApplyTo(queryable, new ODataQuerySettings()
{
PageSize = this.PageSize
});
// Reset the initial URL (to handle "#odata.nextLink")
queryOptions.Request.QueryString = new QueryString(originalQueryString);
return queryEntities;
}

ParitionKey extracted from document doesn't match the one specified in the header - C#

I'm new to CosmosDB and trying to figure out what's going on. I am using Microsoft.Azure.Cosmos NuGet package for development.
This is the line that creates my container:
Container = await database.CreateContainerIfNotExistsAsync(Program.ContainerId, "/id", 400)
This is my class:
public class REProperty
{
public const string PartitionKey = "id";
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
public string Number { get; set; }
public User Owner { get; set; }
And finally the code where I try to create a new document:
ItemResponse<REProperty> Response = await Program.Container.CreateItemAsync<REProperty>(C, new PartitionKey(REProperty.PartitionKey));
I am using the exact same PartitionKey everywhere yet I am still getting this error every time. Am I missing anything obvious?
Error message:
(Message: {"Errors":["PartitionKey extracted from document doesn't match the one specified in the header"]
You've defined the collection to use the id property as the partition key. The value given as id will then be the partition key used. However, you are specifying this:
ItemResponse<REProperty> Response = await Program.Container.CreateItemAsync<REProperty>(C, new PartitionKey(REProperty.PartitionKey));
This will always set the value "id" as the partition key, which is not correct. The actual value is different from document to document. So either you set it like this: new PartitionKey(C.Id) or you just omit the partition key part in the item creation - I think it should be enough to just have the property set, but give it a try to check it.

OAuth and SimpleMembership - how to retrieve ProviderUserId from webpages_OAuthMembership table

I've created an ASP.Net MVC4 web application and it includes the templated functionality that allows users to register with external providers such as Facebook and Twitter. This uses OAuth and SimpleMembership. I'm using Entity Framework code-first, which I'm new to, so I'm finding it difficult to do something really simple.
Once the user has registered with the external provider, a record is created in webpages_OAuthMembership with fields Provider, ProviderUserId and UserId. UserId maps to UserId in the UserProfile table. How do I read the ProviderUserId for the authenticated user? I need it to for use with the FB.api and for other things like retrieving the user photo using https://graph.facebook.com/[ProviderUserId]/picture?type=small.
I have tried this:
SimpleMembershipProvider provider = (SimpleMembershipProvider)Membership.Provider;
string providerUserId = provider.GetUser("[username]", true).ProviderUserKey.ToString();
but ProviderUserKey just returns the UserId rather than ProviderUserId.
There must be a simple way to do this that I'm missing?
This answer assumes using Entity Framework 6 Code First approach (although all the code should work with earlier versions of EF).
I was having issues with accessing Provider and ProviderUserId too. I was attempting to delete a user accounts that authenticated with OAuth. I was able to delete the actual user account from the database with the code:
((SimpleMembershipProvider)Membership.Provider).DeleteAccount(selectedUser); // deletes record from webpages_Membership table
((SimpleMembershipProvider)Membership.Provider).DeleteUser(selectedUser, true); // deletes record from UserProfile table
However, this left the entry in the webpages_OAuthMembership table (which I wanted to delete!).
To solve this, I made a class with the same name as the db table:
public class webpages_OAuthMembership
{
[Key, Column(Order=0)]
public string Provider { get; set; }
[Key, Column(Order = 1)]
public string ProviderUserId { get; set; }
public int UserId { get; set; }
}
The Key and Column data annotations let EF know that Provider and ProviderUserId form a composite key (I think SQL Server calls it a clustered PK). Anyways, in the DbContext class that initializes the database, I did a DbSet on that class:
public DbSet<webpages_OAuthMembership> webpages_OAuthMembership { get; set; }
This now allows you to access the OAuth table in the database in your code
var db = new DbContext(); db.webpages_OAuthMembership.ToList();
// this would give you all entries in the OAuth table
For your case, to get the ProviderUserId, you could use the following code
var OAuthAccount = db.webpages_OAuthMembership.Where(u => u.UserId == userIdOfUserYouAreSearchingFor).FirstOrDefault;
// ProviderUserId would be OAuthAccount.ProviderUserId
where 'userIdOfUserYouAreSearchingFor' is the UserId (from UserProfile table). Make sure to have the FirstOrDefault on the end. That way, if there is no entry in the database for that particular UserId, it will receive null and you can check again that.
Now in my case, I wanted to delete entries from said table. Just in case anyone else comes across this, I will include that info too.
I used this code
var OAuthAcct = ((SimpleMembershipProvider)Membership.Provider).GetAccountsForUser(selectedUser).ToList();
var provider = OAuthAcct[0].Provider;
var providerUserId = OAuthAcct[0].ProviderUserId;
((SimpleMembershipProvider)Membership.Provider).DeleteOAuthAccount(provider, providerUserId);
to delete entries from the webpages_OAuthMembership table.
Hope that helps!

Web API httpget with many parameters

I am trying to create my first REST service using WEB API to replace some of my postbacks in a web forms asp.net project. In the web forms project, when I browse to a new web page, I always get an ASP.net Application variable and a querystring value that helps me determine which database to connect to. In this old app, it connects to several different databases that all have the same schema and database objects but the data is different in each database
I am not sure the best way to pass these variables to a REST Service or if they should be part of the route or some other method.
So in a REST method like the one below
// GET api/<controller>/5
public string GetCategoryByID(int id)
{
return "value";
}
I can get the category id and pass that to my database layer, but I also need the two variables mentioned above. I will need to obtain these variables in every call to my REST api in order to access the appropriate database. Should I use something like the following:
// GET api/<controller>/5
public string GetCategoryByID(int id, string applicationEnvironment, string organization)
{
return "value";
}
Or should they be part of the route with something like this:
api/{appEnvironment}/{organization}/{controller}/{id}
This seems like a simple problem, but I am having trouble figuring out a solution.
I ended up passing extra parameters with my httpget call. I will probably follow this pattern unless I get some additional feedback.
[HttpGet]
public Company[] GetProgramCompanies(int id, [FromUri] string org, [FromUri] string appEnvir)
{
DataLayer dataAccess = new DataLayer(Utilities.GetConnectionString(org, appEnvir));
IEnumerable<BudgetProgramCompanyListing> companies = dataAccess.GetProgramCompaniesListing(id).OrderBy(o => o.Company_Name);
Company[] returnComps = new Company[companies.Count()];
int count = 0;
foreach (BudgetProgramCompanyListing bpc in companies)
{
returnComps[count] = new Company
{
id = bpc.Company_ID,
name = bpc.Company_Name
};
count++;
}
return returnComps;
}
Calling the above service with this url:
api/programcompanies/6?org=SDSRT&appEnvir=GGGQWRT
In .Net core 1.1 you can specify more parameters in HttGet attribute like this:
[HttpGet("{appEnvironment}/{organization}/{controller}/{id}")]
It may work in other .Net versions too.
I used to follow the below two method to pass multiple parameter in HttpGet
public HttpResponseMessage Get(int id,[FromUri]int DeptID)
{
EmpEntity = new EmpDBEntities();
var entity = EmpEntity.USP_GET_EMPINFO(id, DeptID).ToList();
if(entity.Count()!=0)
{
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Employee With ID=" + id.ToString() + " Notfound");
}
}
and the webapi url will be http://localhost:1384/api/emps?id=1&DeptID=1
in the above methode USP_GET_EMPINFO is the stored procedure with two parameters.
in second method we can use the class with [FromUri] to pass multiple parameter.
the code snippet is as below
public HttpResponseMessage Get(int id,[FromUri]Employee emp)
{
EmpEntity = new EmpDBEntities();
var entity = EmpEntity.USP_GET_EMPINFO(id,emp.DEPTID).ToList();
if(entity.Count()!=0)
{
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Employee With ID=" + id.ToString() + " Notfound");
}
}
and the webapi url will be http://localhost:1384/api/emps?id=1&DEPTID=1
here the DEPTID is one of the property of the class. we can add multiple parameters separated with & in the url
You could also define a model and send that with the request and bind it to a variable in your api function using [FromBody].
Something like:
[HttpGet]
public Company[] GetProgramCompanies([FromBody] YourModel model) { ... }
As explained here Model binding in Asp.Net Core

Resources