Error code 404 or 405, for GET and PUT methods - mongodb

Good day I have created a simple Web API for my app.
I was able to successfully make a POST method without conflict, and also GET without parameters(https://www.something.com/api/something/) works too, but when I insert parameter for my GET (https://www.something.com/api/something/1) it gives me a 404 on POSTMAN. When I try PUT Method, and I don't put a parameter, it gives me a 405. And 404 when I put a parameter. Below is my code.
I'm using MongoDB for my database.
database has _id, and category as Partition key.
_id also works as id(Property name JSON)
Controller
// To get a specific record
[HttpGet("{id:length(24)}")]
public ActionResult< SomeModel > Get(string id)
{
var some = _someThing.Get(id);
if (some == null)
{
return NotFound();
}
return some;
}
// For Updating a record
[HttpPut("{id:length(24)}")]
public IActionResult Update(string id, SomeModel pModel)
{
var something = _someModel.Get(id);
if (something == null)
{
return NotFound();
}
_someModel.Update(id, pModel);
return NoContent();
}
Services
// For Finding a specific record
public SomeModel Get(string id) =>
_scores.Find< SomeModel >(scores => scores.id == id).FirstOrDefault();
// For Updating record
public void Update(string id, SomeModel newScore) =>
_scores.ReplaceOne(scores => scores.id == id, newScore);

From this Microsoft page section about route constraints:
Length: Matches a string with the specified length or within a specified range of lengths.
It looks to me like your controller is expecting an ID of 24 characters long.
Try changing it to this:
[HttpGet("{id:length(1,24)}")]
Or using minlength or maxlength instead.

Related

storing object in cosmos db returns bad request?

I seem to be unable to store a simple object to cosmos db?
this is the database model.
public class HbModel
{
public Guid id { get; set; }
public string FormName { get; set; }
public Dictionary<string, object> Form { get; set; }
}
and this is how I store the data into the database
private static void SeedData(HbModelContext dbContext)
{
var cosmosClient = dbContext.Database.GetCosmosClient();
cosmosClient.ClientOptions.AllowBulkExecution = true;
if (dbContext.Set<HbModel>().FirstOrDefault() == null)
{
// No items could be picked hence try seeding.
var container = cosmosClient.GetContainer("hb", "hb_forms");
HbModel first = new HbModel()
{
Id = Guid.NewGuid(),//Guid.Parse(x["guid"] as string),
FormName = "asda",//x["name"] as string,
Form = new Dictionary<string, object>() //
}
string partitionKey = await GetPartitionKey(container.Database, container.Id);
var response = await container.CreateItemAsync(first, new PartitionKey(partitionKey));
}
else
{
Console.WriteLine("Already have data");
}
}
private static async Task<string> GetPartitionKey(Database database, string containerName)
{
var query = new QueryDefinition("select * from c where c.id = #id")
.WithParameter("#id", containerName);
using var iterator = database.GetContainerQueryIterator<ContainerProperties>(query);
while (iterator.HasMoreResults)
{
foreach (var container in await iterator.ReadNextAsync())
{
return container.PartitionKeyPath;
}
}
return null;
}
but when creating the item I get this error message
A host error has occurred during startup operation '3b06df1f-000c-4223-a374-ca1dc48d59d1'.
[2022-07-11T15:02:12.071Z] Microsoft.Azure.Cosmos.Client: Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: 24bac0ba-f1f7-411f-bc57-3f91110c4528; Reason: ();.
Value cannot be null. (Parameter 'provider')
no idea why it fails?
the data should not be formatted incorreclty?
It also fails in case there is data in the dictionary.
What is going wrong?
There are several things wrong with the attached code.
You are enabling Bulk but you are not following the Bulk pattern
cosmosClient.ClientOptions.AllowBulkExecution = true is being set, but you are not parallelizing work. If you are going to use Bulk, make sure you are following the documentation and creating lists of concurrent Tasks. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/tutorial-sql-api-dotnet-bulk-import#step-6-populate-a-list-of-concurrent-tasks. Otherwise don't use Bulk.
You are blocking threads.
The call to container.CreateItemAsync(first, new PartitionKey("/__partitionKey")).Result; is a blocking call, this can lead you to deadlocks. When using async operations (such as CreateItemAsync) please use the async/await pattern. Reference: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#avoid-using-taskresult-and-taskwait
The PartitionKey parameter should be the value not the definition.
On the call container.CreateItemAsync(first, new PartitionKey("/__partitionKey")) the Partition Key (second parameter) should be the value. Assuming your container has a Partition Key Definition of /__partitionKey then your documents should have a __partitionKey property and you should pass the Value in this parameter of such property in the current document. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/troubleshoot-bad-request#wrong-partition-key-value
Optionally, if your documents do not contain such a value, just remove the parameter from the call:
container.CreateItemAsync(first)
Be advised though that this solution will not scale, you need to design your database with Partitioning in mind: https://learn.microsoft.com/azure/cosmos-db/partitioning-overview#choose-partitionkey
Missing id
The model has Id but Cosmos DB requires id, make sure the content of the document contains id when serialized.

NET Core MongoDb Update/Replace exclude fields

I'm trying to complete a general repository for all of the entities in my application. I Have a BaseEntity with property Id, CreatorId and LastModifiedUserId. Now I'd like to Update a record in a collection, without having to modify the field CreatorId, so I have (from the client) an Entity valorized with some fields updated that I want to update.
Hi have 2 ways:
UpdateOneAsync
ReplaceOneAsync
The repo is created like this:
public class BaseRepository<T> : IRepository<T> where T : BaseEntity
{
public async Task<T> Replace/Update(T entity){...}
}
So it's very hard to use Update(1), since I should retrieve with reflection all the fields of T and exclude the ones that I don't want to update.
With Replace(2) I cannot find a way to specify which fields i should exclude when replacing an object with another. Projectionproperty in FindOneAndReplaceOptions<T>() just excludes the fields on the document that is returned after the update.
Am I missing a way in the replace method to exclude the fields or should I try to retrieve the fields with reflection and use a Update?
I don't know if this solution is ok for you .. what i do is :
Declare in Base Repo a method like
public virtual bool Update(TEntity entity, string key)
{
var result = _collection.ReplaceOne(x => x.Id.Equals(key), entity, new UpdateOptions
{
IsUpsert = false
});
return result.IsAcknowledged;
}
then in your controller when you want to update your entities is there where you set the prop you want to change .. like:
[HttpPut]
[ProducesResponseType(typeof(OrderDTO), 200)]
[ProducesResponseType(400)]
public async Task<ActionResult<bool>> Put([FromBody] OrderDTO value)
{
try
{
if (!ModelState.IsValid) return BadRequest(ModelState);
var orderOnDb = await _orderService.FindAsync(xx => xx.Id == value.Id);
if (orderOnDb == null) return BadRequest(Constants.Error.NOT_FOUND_ON_MONGO);
// SET PROPERTY TO UPDATE (MANUALLY)
orderOnDb.LastUpdateDate = DateTime.Now;
orderOnDb.PaymentMethod = value.PaymentMethod;
orderOnDb.StateHistory = value.StateHistory;
//Save on db
var res = await _orderRepo.UpdateAsync(orderOnDb, orderOnDb.Id);
return res;
}
catch (Exception ex)
{
_logger.LogCritical(ex, ex.Message);
throw ex;
}
}
Hope it helps you!!!

Share User data between different action in the same controller in MVC

I want to store globally the object User (that is the table USER in my db) in my HomeController, in that way i don't have to instantiate it in every single action.
I found the following solution that works pretty fine
public class HomeController : Controller
{
private DatabaseContext db = new DatabaseContext();
private User currentUser;
private User CurrentUser
{
get
{
if (User.Identity.IsAuthenticated)
//This function returns the object "User" (table USER in db) based on the PK of the table
currentUser = CustomDbFunctions.GetUserEntityFromUsername(User.Identity.Name, db);
return currentUser;
}
}
public ActionResult Index()
{
if (User.Identity.IsAuthenticated)
return View(CurrentUser);
else
return Redirect("login");
}
}
I'd like to know if there's a better (or more elegant) way to achieve the same goal.
Please note that i'm not using the MembershipProvider.
In your example the user object is instantiated in every single action (in contrast to what you said). This is because actions are usually invoked per http request and controller instances are disposed after each use.
Your code shares the instance structurally (you don't have to repeat the code) which is ok but what about sharing the code between different controllers? I'd suggest to refactor your GetUserEntityFromUsername a little bit so that you retrieve the object only once per request, using the Items container to get the request scope:
public class CustomDbFunctions
{
const string itemsUserKey = "_itemsUserKey";
public static User GetUserEntityFromUsername( IPrincipal principal, DatabaseContext db )
{
if ( principal == null || principal.Identity == null ||
!principal.Identity.IsAuthenticated
)
return null;
if ( HttpContext.Current.Items[itemsUserKey] == null )
{
// retrieve the data from the Db
var user = db.Users.FirstOrDefault( u => u.Name == User.Identity.Name );
HttpContext.Current.Items[itemsUserKey] = user;
}
return (User)HttpContext.Current.Items[itemsUserKey];
}
This way your wrapper takes care of retrieving the instance from the database once per request.
Note that this requires sharing your database context as entities should not be reused on different contexts. Fortunately, this can be done in a similar way:
public class CustomDbFunctions
{
const string dbUserKey = "dbUserKey";
public static DatabaseContext CurrentDatabaseContext
{
get
{
if ( HttpContext.Current.Items[dbUserKey] == null )
{
DatabaseContext ctx = new DatabaseContext(); // or any other way to create instance
HttpContext.Current.Items[dbUserKey] = ctx;
}
return (DatabaseContext)HttpContext.Current.Items[dbUserKey];
}
}
This way, the context instance, shared per request, is always available as
CustomDbFunctions.CurrentDatabaseContext

Efficient method to Update using JAX-RS

I am working on a JPA/Jersey web app and want to know if there is a better way to update a record. Currently, I am doing:
#PUT
#Path("update/{id}")
#Produces("application/json")
#Consumes("application/x-www-form-urlencoded")
public Response createDevice(
#PathParam("id") int id,
#FormParam("name") String name,
#FormParam("type") int type
/*MultivaluedMap<String, String> formParams*/
) {
try {
Devices newDevice = entityManager.find(Devices.class, id);
if(name==null){name=newDevice.getName();}
if(type != newDevice.getType()){newDevice.setType(type);}
newDevice.setName(name);
//newDevice.setType(type);
entityManager.merge(newDevice);
return Response.status(201).entity(new ResponseObject("success")).build();
} finally {
entityManager.close();
}
}
This works, but if my Devices table had more fields, I would have to check for equality of ALL fields with the values on the original object to see if they've changed, so that my
entityManager.merge(newDevice);
will only change the values passed in.
Is there a better way to do this?

ApplyCurrentValues does not seem to work

I try to do the following with entity framework 4 :
public void Update(Site entity)
{
using (db)
{
db.Sites.Attach(db.Sites.Single(s => s.Id == entity.Id));
db.Sites.ApplyCurrentValues(entity);
db.SaveChanges();
}
}
But when i try to update a site through this method i get an error telling me that :
The conversion of a datetime2 data
type to a datetime data type resulted
in an out-of-range value. The
statement has been terminated.
And this is because the original site for some reason is not loaded through the Attach() method.
Can someone help with this ?
/Martin
You don't need to "attach" something you are already retrieving (Ladislav is right). Once you retrieve an entity (e.g SingleOrDefault), it is "in the graph" (EF memory - so it can do optimistic concurrency).
If your trying to do an UPDATE< and the "entity" your passing through is new/detached...
Try the stub technique:
public void Update(Site entity)
{
using (db)
{
var stub = new Site { Id = entity.Id }; // create stub with given key
db.Sites.Attach(stub); // stub is now in graph
db.Sites.ApplyCurrentValues(entity); // override graph (stub) with entity
db.SaveChanges();
}
}
That being said, the error you have provided points to some other issue (data conversion).
Have you checked the "date" values you are passing through with the data type on the model?
public ActionResult Edit(int id, Client collection)
{
try
{
// make sure the rec is in the context
var rec = dbEntities.Clients.First(r => r.ClientID == id);
// update the rec in the context with the parm values
dbEntities.Clients.ApplyCurrentValues(collection);
// make the changes permanent
dbEntities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}