Can an OData controller return an Excel? - asp.net-core

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");
});
}

Related

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 :(

OData integration with CosmosDb does not return expected result

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();

Difference between UseRouter and UseEndpoints

I want to handle requests for a particular path in ASP.NET Core without using controllers.
It seems that I have two options now:
Using the app.UseRouter(r => r.MapPost(...)):
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouter(r => {
r.MapPost("foo/{fooId:int}/bar", (request, response, routeData) =>
{
// My logic
response.StatusCode = StatusCodes.Status200OK;
return Task.CompletedTask;
});
});
}
Using the app.UseEndpoints(e => e.MapPost(...)):
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapPost("foo/{fooId:int}/bar", context =>
{
// My logic
context.Response.StatusCode = StatusCodes.Status200OK;
return Task.CompletedTask;
});
});
}
Both options seem to behave identically.
What is the principal difference between the two and which one should I use?
As far as I understand:
UseRouter is the old way of routing (defined in .net core 2.1)
UseEndpoints is the new way of routing (defined in .net core 3.0)
UseEndpoints supports metadata, context.GetEndpoint() and other powerful features. So it is the preferred option.
UPDATE: Starting from .net 6 both of these approaches are no longer recommended and are replaced with Minimal API.

How to tell when a self-hosted ASP.NET Core application is ready to receive requests?

I need to launch worker processes that communicate using ASP.NET Core Web API. I need to know when I can start sending requests to that process. The only options I see so far are to have the worker call the parent process API when it has finished configuring or poll the worker with an "are you alive" request.
Is there any built in mechanism for this? Any better patterns or designs?
In general, after the application is started successfully, you will be able to send request.
For Application Start event, you could try IHostApplicationLifetime in .net core 3.0, if you are using previous version, you could try IApplicationLifetime which will be obsolete in future version.
Here is a demo which is used to register event while application is started.
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.AddControllersWithViews().AddNewtonsoftJson();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime hostApplicationLifetime)
{
hostApplicationLifetime.ApplicationStarted.Register(() => {
Console.WriteLine("Application is Started");
});
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.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}

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