OData integration with CosmosDb does not return expected result - asp.net-core

I have created a .NET Core 3.1 WebAPI application which connect with Azure Cosmos Db. The WebAPI is returning data from CosmosDb correctly. When I tried to integrate OData to this solution, and tried to query data using the Select method, it does not return expected result.
The following are my code:
StartUp.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddOData();
services.AddControllersWithViews();
services.AddSingleton<ICosmosDbService>(InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=ToDo}/{action=Index}/{id?}");
endpoints.EnableDependencyInjection();
endpoints.Select().Filter().OrderBy().Expand();
});
}
}
WebAPI controller:
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class ItemsController : ControllerBase
{
private readonly ICosmosDbService _cosmosDbService;
public ItemsController(ICosmosDbService cosmosDbService)
{
_cosmosDbService = cosmosDbService;
}
// GET: api/<ItemsController>
[HttpGet]
[EnableQuery()]
public async Task<IEnumerable<Item>> Get()
{
return await _cosmosDbService.GetItemsAsync("SELECT * FROM c");
}
}
When I try to retrieve data using the API call(https://localhost:44357/api/items), I am getting expected result:
[{"id":"5f4f5d02-9217-4591-8f8c-2af9fe7d9ae4","name":"Brush","description":"Brush your teeth every night","completed":true,"partitionKey":null},{"id":"6a5edfe3-9c84-4398-bed4-963dbb4a42e3","name":"Excercise","description":"Hit the gym in the evening","completed":true,"partitionKey":null}]
But when I try to use the OData method(https://localhost:44357/api/items?$select=name), I am not getting expected result. Instead, I am getting this:
[{"instance":null,"container":{},"modelID":"7c0ae376-1666-46f8-886f-9bf758824063","untypedInstance":null,"instanceType":null,"useInstanceForProperties":false},{"instance":null,"container":{},"modelID":"7c0ae376-1666-46f8-886f-9bf758824063","untypedInstance":null,"instanceType":null,"useInstanceForenter code hereProperties":false}]
Any idea why it is like this?

There is incompatible situation with the JSON serializer in Asp.Net 3.1. Try to AddNewtonsoftJson.
services.AddControllers(mvcOptions =>
mvcOptions.EnableEndpointRouting = false)
.AddNewtonsoftJson();

Related

Can an OData controller return an Excel?

Using ASP.Net Core 6. OData V4 (Nuget 8.0.10). EF Core 6. Blazor WASM client.
When an ODATA query from my client application hits the back end API, it ALWAYS sends its ODATA query to the IQueryable Get() end point / method as below. It uses generics to allow just about any entity/table to be queried.
As you can imagine, the ODATA middleware does a great job of intercepting these requests, converting the ODATA query into something that EF Core can interpret and then returns the result set as JSON back to the client.
ATM the client application always displays the result set in a data grid for the user.
However, rather than have ODATA (or whatever is that does it) return the data as JSON, I'd like to create and return an XLSX file instead.
How might I go about this? I can't determine when or where ODATA applies its query in order to produce a result set based on the ODATA query that the client fired in. Is there some middleware I can tap into? Any pointers would be nice! I do not need help with XLSX file creation itself.
Thank you.
Controller:
[ApiController]
[Route("api/[controller]")]
public class GenericBaseController<T> : ODataController where T : BaseEntity
{
private readonly IGenericRepositoryServer<T> crud;
public GenericBaseController(IGenericRepositoryServer<T> _crud)
{
crud = _crud;
}
[HttpGet("Post")]
public virtual IQueryable<T> Get()
{
return crud.Query().AsNoTracking();
}
...
Startup.cs. ConfigureServices:
...
services.AddControllers()
.AddOData(opt => opt.AddRouteComponents("odata", MyDBContext.GetEdmModel())
.Count()
.Select()
.Filter()
.Expand()
.OrderBy()
.SkipToken()
);
...
And Configure in Startup.cs:
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
app.UseODataRouteDebug();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseODataQueryRequest();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}

CORS issue with PUT request from reactjs to asp.netCore api

I am working on a Reactjs app with Asp.netCore API with Sql database
deployed to IIS server.
All the operation is working except PUT operation on the task
it break with CORS error and the request is not reaching the backend, I tested it locally and it's working fine. Tested using Postman and it's working too even the deployed version is working with postman. I can't figure out what's the issue or from where I should start debugging.
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy(name: CorsPolicy, builder =>
{
builder.WithOrigins(FrontEnd_URL).SetIsOriginAllowed((host) =>
true).AllowAnyHeader().AllowAnyMethod().AllowCredentials();
}));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseCors(CorsPolicy);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapHub<TrelloHub>("/trello");
});
}
TaskController
[Route("/api/tasks")]
[ApiController]
public class TasksController : Controller
{
private readonly IMapper _mapper;
private readonly ITaskService _taskService;
public TasksController(ITaskService taskService , IMapper mapper)
{
_mapper = mapper;
_taskService = taskService;
}
[HttpPut("{id:int}", Name = "UpdateTask")]
public async Task<String> UpadateTask([FromBody]TaskDto taskdto, int id)
{
var taskModel = _mapper.Map<Task>(taskdto);
return await _taskService.UpadateTask(id , taskModel);
}
}
first check IIS server logs. it can help you a lot, also look at the request headers of the reactjs client origin: header exactly and add it to the allowed origins

Blazor Server get current logged user when EF DbContext execute select data query

I build blazor server multi tenant application and I wants to use HasQueryFilter in entity framework DbContext for predefined filter with TenantId. Ofcourse I have connected User with tenant. I created CurrentTenatnProvider which has method GetCurrentTenatnId. In this mehtod i use AuthentificationStateProvider and call GetAuthenticationStateAsync(). Ofcourse i get the error ''GetAuthenticationStateAsync was called before SetAuthenticationState". I Cannot use IHttpContextAccessor because in Azure app I get null reference exception.
Is there any other possibility how to get CurrentUser in time when DbContext execute select data query?
I was thinking about cache CurrnetUser but there is problem with cache key.CurrentTenatnProvider service is registered as scoped service. There is Id attribute which is set in constructor. And then is used as cache key. But this approach does not working and I get the same error.
It si possible get signal-r connection identificator and use it as cache key?
I spended 2 days with test lots of combination and read lots of documentation but unfortunately I didn't find any solution. I will be very grateful for any advice.
I have done this a few ways but the simplest way I could find was retrieving my user from the database in the MainLayout.razor file during OnInitializedAsync() and passing it in a fixed cascading parameter <CascadingValue Name="CurrentUser" IsFixed="true" Value="UserId">. From there, I could reference it where needed in any child component.
finally I found solution! From my view it is bug! Problem is because services.AddDbContextFactory is registered as Singleton. I create my own implementation of IDbContext factory and register it as Scoped. After this change everything’s works perfect. When I change registration scope of DbContextFactory to singleton, I get the error: GetAuthenticationStateAsync was called before SetAuthenticationState.
My DbContextFactory
public class BlazorContextFactory<TContext> : IDbContextFactory<TContext> where TContext : DbContext
{
private readonly IServiceProvider provider;
public BlazorContextFactory(IServiceProvider provider)
{
this.provider = provider;
}
public TContext CreateDbContext()
{
if (provider == null)
{
throw new InvalidOperationException(
$"You must configure an instance of IServiceProvider");
}
return ActivatorUtilities.CreateInstance<TContext>(provider);
}
}
My StartUp
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ApplicationDbContext>();
services.AddScoped<IDbContextFactory<ApplicationDbContext>, BlazorContextFactory<ApplicationDbContext>>();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddSingleton<WeatherForecastService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
I hope it's help other peoples! I spend 6 days with this problem :(

Identity core - cannot log in after change key type

I try to setup identity in my application. I created Razor Pages project and I added New scaffolded item -> Identity. Then appeared new folder - Areas which contains all identity related stuff.
One thing I want to change is change Primary Key for User. I want int instead of Guid. I followed many tutorials and posts on this site, but something is wrong. I am able to register new user, but I cannot login. If I try login, I am redirected to homepage but I still see Login link instead of Logout and of course all views marked with [Authorize] are inacessible for me.
I will show what I changed, and I believe that one of you will notice one piece of code I missing.
IdentityContext
public class ApplicationRole : IdentityRole<int> { }
public class ApplicationUserRole : IdentityUserRole<int> { }
public class ApplicationUser : IdentityUser<int> { }
public class IdentityContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>
{
public IdentityContext(DbContextOptions<IdentityContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
IdentityHostingStartup
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
services.AddDbContext<IdentityContext>(options =>
options.UseSqlServer(
context.Configuration.GetConnectionString("IdentityContextConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<IdentityContext>();
});
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
And I have changed all occurrences of IdentityUser into ApplicationUser
You're implementing both an IdentityContext and ApplicationDbContext, both of which inherit from IdentityDbContext and use your customized Identity entities. It's hard to say with the code you've provided, but my best guess is that you're creating the user with one, and attempt to login with the other. You only need one of these contexts, not both. Remove one and then ensure that everything is using the same context.

with asp.net core and ef core when i try to seed there went wrong

My startup.cs is
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
SeedData.Seed(app);
}
and my seed class is :
public static class SeedData
{
public static void Seed(IApplicationBuilder app)
{
var _dbContext= app.ApplicationServices.GetRequiredService<BlogDbContext>();
_dbContext.Database.EnsureDeleted();
_dbContext.Add<User>(new User { UserName = "Niko", Password ="123",EmailAddress="nikozhao5456#gmail.com",UserType= Models.User.Type.Visitor,RegistDate=System.DateTime.Now});
_dbContext.Add<Admin>(new Admin{EmailAddress="lovezgd888#163.com",Password="123"});
_dbContext.SaveChanges();
}
}
when I Update-Database in the Nuget Package Manage :
An error occurred while calling method 'BuildWebHost' on class 'Program'. Continuing without the application service provider. Error: Cannot resolve scoped service 'Blog.DAL.Context.BlogDbContext' from root provider.
and
Unable to create an object of type 'BlogDbContext'. Add an implementation of 'IDesignTimeDbContextFactory' to the project, or see https://go.microsoft.com/fwlink/?linkid=851728 for additional patterns supported at design time.
Well Ive solve it by watching the docs,There is something different between Asp.Net Core 1.x and 2.0;I just should write the seed method in the program.cs