"Microsoft.AspNet.Mvc": "6.0.0-rc1-final" Custom View Engine - model-view-controller

I'm on "Microsoft.AspNet.Mvc": "6.0.0-rc1-final" and
I don't see the configure method on AddMvc any longer and AddViewOptions.ViewEngine will throw
services.AddMvc().AddViewOptions(o =>
{
o.ViewEngines.Add(typeof(ICANRazorViewEngine));
o.ViewEngines.Clear();
});
gives me can't convert System.Type to IViewEngine?
What am i missing here?

At the time of this writing (Mar-10-2016) the official ASP.NET Documentation to "Creating a Custom View Engine" (Page 299) was not available.
I was getting the same error using "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", but because my intent was just include additional view locations, I fix it with:
public class CustomViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context)
{
}
public virtual IEnumerable<string> ExpandViewLocations(
ViewLocationExpanderContext context,
IEnumerable<string> viewLocations)
{
return viewLocations.Union(new string[] { "~/Views/{1}/PartialViews/{0}.cshtml" });
}
}
and adding the code to Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddRazorOptions(options =>
{
options.ViewLocationExpanders.Add(new CustomViewLocationExpander());
})
.AddViewLocalization(LanguageViewLocationExpanderFormat.SubFolder);
}
I hope that could help you in some way.

Related

OData Controller withing Asp.Net Core MVC application

I'm working on a project in ASP .NET Core 3.1 MVC now I want to add some API controllers to return list of objects.
For this I want to use OData Controller version 8.0.0 so I can get quarriable data to improve performance on large data tables
I'm new in ASP .NET Core and OData. can anybody explain how to configure my project's Startup file so I can run both MVC and OData controllers same time.
Kindly share some example code
Firstly, you have a MVC project, since MVC project can also expose API, so OData should also work for MVC project. Firstly, assuming you've integrate ef core and in my workaround, I followed this document to create database and data management view for a model.
Then let's add OData. Install this nuget package: Microsoft.AspNetCore.OData, modify your startup.cs file, please see the Configuration and GetEdmModel method.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using WebMvcNet5.Data;
using WebMvcNet5.Models;
using Microsoft.OData.ModelBuilder;
using Microsoft.AspNetCore.OData;
using Microsoft.OData.Edm;
namespace WebMvcNet5
{
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();
services.AddControllers().AddOData(opt => opt.EnableQueryFeatures().AddRouteComponents("odata", GetEdmModel()));
services.AddDbContext<WebMvcNet5Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("WebMvcNet5Context")));
}
// 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=Home}/{action=Index}/{id?}");
});
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
//My model is Movie, and what I set "GetMovie" here means I need to create a controller named "GetMovieController"
builder.EntitySet<Movie>("GetMovie");
return builder.GetEdmModel();
}
}
}
Then this is my controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using WebMvcNet5.Data;
namespace WebMvcNet5.Controllers
{
[Route("odata/[Controller]")]
public class GetMovieController : Controller
{
private readonly WebMvcNet5Context _context;
public GetMovieController(WebMvcNet5Context context)
{
_context = context;
}
[EnableQuery]
public IActionResult Get()
{
return Ok(_context.Movie);
}
}
}
My test result:
I have managed to fix my issue to run Web Application which exposes OData APIs
Issue was in Startup.cs file
I'm using Asp.Net Core 3.1 and Microsoft.AspNetCore.OData v7.3.0
my Startup.cs file code is:
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<AppUser, AppRole>(opt =>
{
opt.User.RequireUniqueEmail = true;
})
//.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//add services
services.RegisterServices();
services.AddScoped<ViewRendererService>();
services.AddMvc()
.AddMvcOptions(options => options.EnableEndpointRouting = false)
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddOData();
services.AddRouting();
services.AddControllersWithViews();
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, ApplicationDbContext dataContext)
{
if (env.EnvironmentName == "Development")
{
dataContext.Database.Migrate();
app.UseDeveloperExceptionPage();
}
else
{
app.UseDeveloperExceptionPage();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseRequestLocalization();
app.UseMvc(routes =>
{
routes.Select().Filter().OrderBy().Expand().Count().SkipToken().MaxTop(null);
routes.MapODataServiceRoute("odata", "api", GetEdmModel());
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "Finance",
template: "{area:exists}/{controller=Account}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("ProductApi");
builder.EntitySet<ProductUOM>("ProductUomApi");
ActionConfiguration action = builder.EntityType<Product>().Action("GetUOM");
action.Parameter<long>("id");
action.ReturnsCollectionFromEntitySet<Product>("Product");
return builder.GetEdmModel();
}
}
Hope this will help others

How to ignore/alter model parameter in asp.net core with Swashbuckle.AspNetCore.Swagger

My project references the following packages;
Swashbuckle.AspNetCore.Filters v6.0.0
Swashbuckle.AspNetCore.Swagger v5.6.3
Swashbuckle.AspNetCore.SwaggerGen v5.6.3
Swashbuckle.AspNetCore.SwaggerNewtonSoft v5.6.3
Microsoft.AspNetCore.OData v7.5.0
Here's the issue:
I have a controller called "TestController". In it, there is a single [HttpGet] method called Test.
The method is decorated as follows;
[HttpGet]
[SwaggerOperation(OperationId = nameof(Test))]
public IActionResult Test([FromQuery] string id, [FromQuery] ODataQueryOptions<SearchOptions> oData)
{
// ...
}
Since I'm using Swashbuckle, the expected results should be that there is a get method named Test with a bunch of query parameters returned to the documentation UI.
However, instead I see an exception. The exception says;
Failed to generate Scheme for type - ODataQueryOptions<`T>. See inner exception
Inspecting the inner exception shows that swagger is attempting to build what-looks to be a scheme of a bunch of system types (eg. HttpContext, response, request, etc...).
I believe this is happening b/c the ODataQueryOption<`T> class comes with a number of contextual properties to help facilitate URI parsing.
See more about that class here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnet.odata.query.odataqueryoptions?view=odata-aspnetcore-7.0
The exceptions and random google searches have lead me down the path of adding custom IOperationFilter, ISchemeFilters, and IParameterFilters (these all are Swagger specific configuration 'filters').
I've tried to remove the operation all-together by setting properties to null. I've attempted the same with Scheme and Parameter filters... All with no luck. And no documentation to help...
Example of my attempts:
class ParamFilter : IParameterFilter {
public void Apply(OpenApiParameter parameter, ParameterFilterContext context) {
parameter.Scheme = null;
parameter.Reference = null;
}
}
class SchemeFilter : ISchemeFilter {
public void Apply(OpenApiScheme scheme, SchemeFilterContext context) {
scheme.Items = null;
scheme.Reference = null;
scheme.Reference = null;
}
}
// Note: this never gets hit by the debugger. App throws exception before invocation.
class OperationFilter : IOperationFilter {
public void Apply(OpenApiOperation operation, OperationFilterContext context) {
operation.Parameters.clear()
}
}
Nothing worked. Same exception.
At this point my question is fairly simple;
How can I remove the ODataQueryOption parameter from swagger documentation generation?
EDIT: Adding exception messages
Failed to generate Schema for type - Microsoft.AspNet.OData.Query.ODataQueryOptions`1[SearchOptions].
See inner exception
Failed to generate Schema for type -
Microsoft.AspNetCore.Http.HttpRequest. See inner exception
Failed to generate Schema for type -
Microsoft.AspNetCore.Http.HttpContext. See inner exception
Failed to generate Schema for type -
Microsoft.AspNetCore.Http.Authentication.AuthenticationManager. See
inner exception
Could not load type
'Microsoft.AspNetCore.Http.Features.Authentication.AuthenticateContext'
from assembly 'Microsoft.AspNetCore.Http.Features, Version=3.1.8.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60'.
It could work well in my project:
Action(Be sure remove [FromQuery] on ODataQueryOptions):
[HttpGet]
[EnableQuery]
public IActionResult Test Get([FromQuery] string id, ODataQueryOptions<SearchOptions> ODataQueryOptions)
{
//...
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddOData();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
SetOutputFormatters(services);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.EnableDependencyInjection();
endpoints.Select().Filter().Expand().MaxTop(10);
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<WeatherForecast>("WeatherForecast");
return builder.GetEdmModel();
}
private static void SetOutputFormatters(IServiceCollection services)
{
services.AddMvcCore(options =>
{
IEnumerable<ODataOutputFormatter> outputFormatters =
options.OutputFormatters.OfType<ODataOutputFormatter>()
.Where(foramtter => foramtter.SupportedMediaTypes.Count == 0);
foreach (var outputFormatter in outputFormatters)
{
outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata"));
}
});
}
Try setting the mapping for ODataQueryOptions with options.MapType(typeof(ODataQueryOptions<>), () => new ());

ASP.Net Core:Claims Transformation and Authorization Policy

Please have a look at this url first https://weblogs.asp.net/imranbaloch/claims-transformation-and-authorization-policy-in-aspnet5-mvc6
public virtual Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (principal.Identity.IsAuthenticated)
{
// get this from cache or db
var country = "Pakistan";
(principal.Identity as ClaimsIdentity).AddClaim(new Claim("Nationality", country));
}
return Task.FromResult(principal);
}
when TransformAsync() will be called.......how to use it ?
[Authorize(Policy = "MustBePakistani")]
public IActionResult Message()
{
return Content("Hi Pakistani");
}
when Message action will be called then how asp.net mvc system will be able to understand what is user's nationality.....is it pakistani or indian ?
guide me how does it work. thanks
In addition to above two segments of the code in your question, you still need to add policy to authorization services in Startup.cs.
Example,
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.AddPolicy("MustBePakistani", policy =>
policy.RequireClaim("Nationality", "Pakistan"));
});
services.AddSingleton<IClaimsTransformation, ClaimsTransformer>();
...
}
private class ClaimsTransformer : IClaimsTransformation
{
// Can consume services from DI as needed, including scoped DbContexts
public ClaimsTransformer(IHttpContextAccessor httpAccessor)
{
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (principal.Identity.IsAuthenticated)
{
// get this from cache or db
var country = "Pakistan";
(principal.Identity as ClaimsIdentity)
.AddClaim(new Claim("Nationality", country));
}
return Task.FromResult(principal);
}
}
}

Adding custom authorize claim based on local database for Azure user .net core

I am trying to recognize database user with Azure AD email address, and then add custom claim to azure AD authenticated user, based on property from local database user. In startup.cs I got:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IClaimsTransformer, ClaimsTransformer>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, KayttajatContext context)
{
...
app.UseClaimsTransformation(async (c) =>
{
IClaimsTransformer transformer = c.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
return await transformer.TransformAsync(c);
});
...
}
Then ClaimsTransformer.cs looks like this:
namespace Authtest
{
public class ClaimsTransformer : IClaimsTransformer
{
private readonly KayttajatContext _context;
public ClaimsTransformer(KayttajatContext dbContext)
{
_context = dbContext;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext ctrans)
{
string sposti = ((ClaimsIdentity)ctrans.Principal.Identity).Name;
var user = await _context.Henkilöt.FirstOrDefaultAsync(t => t.Sposti == sposti);
if (user.Sposti == sposti)
{
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "Administrator"));
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "User"));
}
else
{
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "User"));
}
return ctrans.Principal;
}
}
}
But this gives me "NullReferenceException: Object reference not set to an instance of an object." at if (user.Sposti == sposti)
If I instead give string value to either one of the variables, if statement works fine. I don't know what I'm doing wrong? Does it have something to do with async? Please help this is driving me nuts.
I was trying to call string sposti = ((ClaimsIdentity)ctrans.Principal.Identity).Name; before it was set. Works now, thanks :)

.Net Core DI to filter class from startup

How do I inject the database to my TestAttribute class as .net core does magically to my controller.
services.AddScoped<DbContextOptions>(p => p.GetRequiredService<DbContextOptions<Context>>());
services.TryAdd(new ServiceDescriptor(typeof(Context), typeof(Context), ServiceLifetime.Transient));
services.AddMvc(options =>
{
options.Filters.Add(new TestAttribute(/*need parameter*/));
}
public HouseController([FromServices] Context database)
{
this.Database = database;
}
public class TestAttribute : ActionFilterAttribute
{
public TestAttribute([FromServices] Context database)
{
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
}
}
To inject dependencies into Filters you need to use the ServiceFilter attribute (see docs)
Manually injecting it to an controller action
[ServiceFilter(typeof(TestAttribute))]
public Task<IActionResult> Index()
{
...
}
Also your registration is wrong in the startup for all actions! Filters.Add() accepts a Type, when you want to use attributes which require dependencies (because you can't instantiate them in code), see "Filter Scopes" in the docs.
services.AddMvc(options =>
{
options.Filters.Add(typeof(TestAttribute));
}

Resources