I add the reference system.web.routing dll and I add namespace in all page and also add in web.config page, but here the error in add query string parameter line. Can someone findthe error and tell the answer?
var isDescending = string.CompareOrdinal(Model.SortBy, ViewData["ColumnName"].ToString()) == 0 && Model.SortAscending;
var routeData = new RouteValueDictionary { { "sortBy", ViewData["ColumnName"].ToString() }, { "ascending", !isDescending } };
// Add in the querystring parameters *except* for the paging ones (as sorting should send us back to the first page of data)
routeData.AddQueryStringParameters().ExceptFor("page", "pageSize");
var htmlAttributes = new Dictionary<string, object>();
if (string.CompareOrdinal(Model.SortBy, ViewData["ColumnName"].ToString()) == 0)
{
if (Model.SortAscending)
{
htmlAttributes.Add("class", "sortAsc");
}
else
{
htmlAttributes.Add("class", "sortDesc");
}
}
}
you have added system.web.routing dll and you have added namespace in all page and also add in web.config page you said that fine
so you have to remove this line and run the program surely u will get output
routeData.AddQueryStringParameters().ExceptFor("page", "pageSize");
Related
I have a generic catch all controller/action that receive files, parse the json content and find out the controller name and action name to be called from that.
Here my previous .NET Framework (old ASP) implementation which worked great:
public async Task<ActionResult> Run(PackingSlip packingSlip, IEnumerable<HttpPostedFileBase> files)
{
var controllerName = packingSlip.service_name;
var actionName = packingSlip.service_object;
// get the controller
var ctrlFactory = ControllerBuilder.Current.GetControllerFactory();
var ctrl = ctrlFactory.CreateController(this.Request.RequestContext, controllerName) as Controller;
var ctrlContext = new ControllerContext(this.Request.RequestContext, ctrl);
var ctrlDescAsync = new ReflectedAsyncControllerDescriptor(ctrl.GetType());
ctrl.ControllerContext = ctrlContext;
// get the action
var actionDesc = ctrlDescAsync.FindAction(ctrlContext, actionName);
// execute
ActionResult result;
if (actionDesc is AsyncActionDescriptor actionDescAsync)
result = await Task.Factory.FromAsync((asyncCallback, asyncState) => actionDescAsync.BeginExecute(ctrlContext, new Dictionary<string, object> { { "packingSlip", packingSlip }, { "files", files } }, asyncCallback, asyncState), asyncResult => actionDescAsync.EndExecute(asyncResult), null) as ActionResult;
else
result = actionDesc.Execute(ctrlContext, new Dictionary<string, object> { { "packingSlip", packingSlip }, { "files", files } }) as ActionResult;
// return the other action result as the current action result
return result;
}
Now with ASP.NET Core (or .NET 5), ControllerBuilder doesn't exist anymore and most of those things changed.
I tried to inject a IControllerFactory and use it, but can't find the proper way to use it to call an action knowing the "controllerName" and "actionName". It should also, like before, determine if it was an async action or not and act accordingly.
Found the answer by myself.
AspCore have an hidden barely documented extension method that registers controllers in the DI container: AddControllersAsServices.
services.AddMvc().AddControllersAsServices();
Then you can use IServiceProvider to resolve your controllers.
I am looking for a way to determine if endpoint requires authorization (.Net Core 3.1) using IOperationFilter.
If Authorization is setup via filter or explicitly as attribute, it can be found in OperationFilterContext context.ApiDescription.ActionDescriptor.FilterDescriptors.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter) and context.ApiDescription.CustomAttributes().OfType<AuthorizeAttribute>().
But if authorization is set as
endpoints.MapControllers().RequireAuthorization();, which should add AuthorizationAttribute to all endpoints, it is not appeared neither in filters nor in attributes. Any thoughts on how to catch if auth is applied to endpoints in this case?
I was able to beat this today like so (swashbuckle 5.63):
Make a new class like this
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace YourNameSpace
{
public class SwaggerGlobalAuthFilter : IOperationFilter
{
public void Apply( OpenApiOperation operation, OperationFilterContext context )
{
context.ApiDescription.TryGetMethodInfo( out MethodInfo methodInfo );
if ( methodInfo == null )
{
return;
}
var hasAllowAnonymousAttribute = false;
if ( methodInfo.MemberType == MemberTypes.Method )
{
// NOTE: Check the controller or the method itself has AllowAnonymousAttribute attribute
hasAllowAnonymousAttribute =
methodInfo.DeclaringType.GetCustomAttributes( true ).OfType<AllowAnonymousAttribute>().Any() ||
methodInfo.GetCustomAttributes( true ).OfType<AllowAnonymousAttribute>().Any();
}
if ( hasAllowAnonymousAttribute )
{
return;
}
// NOTE: This adds the "Padlock" icon to the endpoint in swagger,
// we can also pass through the names of the policies in the List<string>()
// which will indicate which permission you require.
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth2" // note this 'Id' matches the name 'oauth2' defined in the swagger extensions config section below
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
}
};
}
}
}
In swagger config extensions
options.AddSecurityDefinition( "oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
//_swaggerSettings is a custom settings object of our own
AuthorizationUrl = new Uri( _swaggerSettings.AuthorizationUrl ),
Scopes = _swaggerSettings.Scopes
}
}
} );
options.OperationFilter<SwaggerGlobalAuthFilter>();
Put together from docs, other SO and decompiled code of built-in SecurityRequirementsOperationFilter
AFAIK, it is defining a global auth setup for all your routed endpoints except those that explicitly have AllowAnonymousAttribute on controller or endpoint. since, as your original question hints at, using the extension RequireAuthorization() when setting up routing implicitly puts that attribute on all endpoints and the built-in SecurityRequirementsOperationFilter which detect the Authorize attribute fails to pick it up. Since your routing setup effectively is putting Authorize on every controller/route it seems setting up a default global filter like this that excludes AllowAnonymous would be in line with what you are configuring in the pipeline.
I suspect there may be a more 'built-in' way of doing this, but I could not find it.
Apparently, this is an open issue on the NSwag repo as well (for people like me that drive by with the same issue, but with NSwag instead of Swashbuckle):
https://github.com/RicoSuter/NSwag/issues/2817
Where there's also another example of solving the issue (not only securityrequirement, but also its scopes).
I know it's been a long time since this question was asked.
But I was facing a similar issue, and following the advice from an issue in GitHub here, managed to resolve it using this implementation of IOperationFilter (and now works like a charm):
public class AuthorizeCheckOperationFilter : IOperationFilter
{
private readonly EndpointDataSource _endpointDataSource;
public AuthorizeCheckOperationFilter(EndpointDataSource endpointDataSource)
{
_endpointDataSource = endpointDataSource;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var descriptor = _endpointDataSource.Endpoints.FirstOrDefault(x =>
x.Metadata.GetMetadata<ControllerActionDescriptor>() == context.ApiDescription.ActionDescriptor);
var hasAuthorize = descriptor.Metadata.GetMetadata<AuthorizeAttribute>()!=null;
var allowAnon = descriptor.Metadata.GetMetadata<AllowAnonymousAttribute>() != null;
if (!hasAuthorize || allowAnon) return;
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
operation.Security = new List<OpenApiSecurityRequirement>
{
new()
{
[
new OpenApiSecurityScheme {Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth2"}
}
] = new[] {"api1"}
}
};
}
}
The issue stated this:
ControllerActionDescriptor.EndpointMetadata only reflects the metadata
discovered on the controller action. Any metadata configured via the
endpoint APIs do not show up here. It was primarily the reason we
documented it as being infrastructure-only since it's a bit confusing
to use.
There's a couple of options you could use
a) You could decorate your controllers using [Authorize]. That should allow the metadata to show up in the property.
b) You could look up the metadata by reading from EndpointDataSource.
In AspNetCore, given a FilterContext, I'm looking to get a route template e.g.
{controller}/{action}/{id?}
In Microsoft.AspNet.WebApi I could get the route template from:
HttpControllerContext.RouteData.Route.RouteTemplate
In System.Web.Mvc I could get this from:
ControllerContext.RouteData.Route as RouteBase
In AspNetCore there is:
FilterContext.ActionDescriptor.AttributeRouteInfo.Template
However, not all routes are attribute routes.
Based on inspection if the attribute is not available, default routes and/or mapped routes can be assembled from:
FilterContext.RouteData.Routers.OfType<Microsoft.AspNetCore.Routing.RouteBase>().First()
but I'm looking for a documented or a simply better approach.
Update (24 Jan 2021)
There is a much much simpler way of retrieving the RoutePattern directly via the HttpContext.
FilterContext filterContext;
var endpoint = filterContext.HttpContext.GetEndpoint() as RouteEndpoint;
var template = endpoint?.RoutePattern?.RawText;
if (template is null)
throw new Exception("No route template found, that's absurd");
Console.WriteLine(template);
GetEndpoint() is an extension method provided in EndpointHttpContextExtensions class inside Microsoft.AspNetCore.Http namespace
Old Answer (Too much work)
All the route builders for an ASP.NET Core app (at least for 3.1) are exposed and registered via IEndpointRouteBuilder, but unfortunately, this is not registered with the DI container, so you can't acquire it directly.The only places where I have seen this interface being exposed, are in the middlewares.
So you can build a collection or dictionary out of one of those middlewares, and then use that for your purposes.
e.g
Program.cs
Extension class to build your endpoint collection / dictionary
internal static class IEndpointRouteBuilderExtensions
{
internal static void BuildMap(this IEndpointRouteBuilder endpoints)
{
foreach (var item in endpoints.DataSources)
foreach (RouteEndpoint endpoint in item.Endpoints)
{
/* This is needed for controllers with overloaded actions
* Use the RoutePattern.Parameters here
* to generate a unique display name for the route
* instead of this list hack
*/
if (Program.RouteTemplateMap.TryGetValue(endpoint.DisplayName, out var overloadedRoutes))
overloadedRoutes.Add(endpoint.RoutePattern.RawText);
else
Program.RouteTemplateMap.Add(endpoint.DisplayName, new List<string>() { endpoint.RoutePattern.RawText });
}
}
}
public class Program
{
internal static readonly Dictionary<string, List<string>> RouteTemplateMap = new Dictionary<string, List<string>>();
/* Rest of things */
}
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
/* all other middlewares */
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
//Use this at the last middlware exposing IEndpointRouteBuilder so that all the routes are built by this point
endpoints.BuildMap();
});
}
And then you can use that Dictionary or Collection, to retrieve the Route Template from the FilterContext.
FilterContext filterContext;
Program.RouteTemplateMap.TryGetValue(filterContext.ActionDescriptor.DisplayName, out var template);
if (template is null)
throw new Exception("No route template found, that's absurd");
/* Use the ActionDescriptor.Parameters here
* to figure out which overloaded action was called exactly */
Console.WriteLine(string.Join('\n', template));
To tackle the case of overloaded actions, a list of strings is used for route template (instead of just a string in the Dictionary)
You can use the ActionDescriptor.Parameters in conjunction with RoutePattern.Parameters to generate a unique display name for that route.
These are the assembled versions, but still looking for a better answer.
AspNetCore 2.0
FilterContext context;
string routeTemplate = context.ActionDescriptor.AttributeRouteInfo?.Template;
if (routeTemplate == null)
{
// manually mapped routes or default routes
// todo is there a better way, not 100% sure that this is correct either
// https://github.com/aspnet/Routing/blob/1b0258ab8fccff1306e350fd036d05c3110bbc8e/src/Microsoft.AspNetCore.Routing/Template/TemplatePart.cs
IEnumerable<string> segments = context.RouteData.Routers.OfType<Microsoft.AspNetCore.Routing.RouteBase>()
.FirstOrDefault()?.ParsedTemplate.Segments.Select(s => string.Join(string.Empty, s.Parts
.Select(p => p.IsParameter ? $"{{{(p.IsCatchAll ? "*" : string.Empty)}{p.Name}{(p.IsOptional ? "?" : string.Empty)}}}" : p.Text)));
if (segments != null)
{
routeTemplate = string.Join("/", segments);
}
}
AspNetCore 3.0 with Endpoint Routing
RoutePattern routePattern = null;
var endpointFeature = context.HttpContext.Features[typeof(Microsoft.AspNetCore.Http.Features.IEndpointFeature)]
as Microsoft.AspNetCore.Http.Features.IEndpointFeature;
var endpoint = endpointFeature?.Endpoint;
if (endpoint != null)
{
routePattern = (endpoint as RouteEndpoint)?.RoutePattern;
}
string formatRoutePart(RoutePatternPart part)
{
if (part.IsParameter)
{
RoutePatternParameterPart p = (RoutePatternParameterPart)part;
return $"{{{(p.IsCatchAll ? "*" : string.Empty)}{p.Name}{(p.IsSeparator ? " ? " : string.Empty)}}}";
}
else if (part.IsLiteral)
{
RoutePatternLiteralPart p = (RoutePatternLiteralPart)part;
return p.Content;
}
else if(part.IsSeparator)
{
RoutePatternSeparatorPart p = (RoutePatternSeparatorPart)part;
return p.Content;
}
else
{
throw new NotSupportedException("Unknown Route PatterPart");
}
}
if (routePattern != null)
{
// https://github.com/aspnet/Routing/blob/1b0258ab8fccff1306e350fd036d05c3110bbc8e/src/Microsoft.AspNetCore.Routing/Template/TemplatePart.cs
routeString = string.Join("/", routePattern.PathSegments.SelectMany(s => s.Parts).Select(p => formatRoutePart(p)));
}
My requirement is to fetch html data from database and render it on view. But if that string contains #Html.Action("actionName","controllerName"), i need to call perticular controller action method also.
I am rendering my html on view using #Html.Raw().
Eg: Below is the html string stored in my database
'<h2> Welcome To Page </h2> <br/> #Html.Action("actionName", "controllerName")'
So when it render the string, it execute mentioned controller and action too.
Any help will be appreciated.
You can try RazorEngine to allow string template in razor executed.
For example, sample code from the project site http://antaris.github.io/RazorEngine/:
using RazorEngine;
using RazorEngine.Templating; // For extension methods.
string template = "Hello #Model.Name, welcome to RazorEngine!";
var result =
Engine.Razor.RunCompile(template, "templateKey", null, new { Name = "World" });
But there is one catch, Html and Url helpers are defined in the Mvc framework, hence it is not supported by default.
I will suggest you try to create your template by passing model so that you don't have to use #Html.Action.
If you can not avoid it, then there is possible a solution suggested by another so answer https://stackoverflow.com/a/19434112/2564920:
[RequireNamespaces("System.Web.Mvc.Html")]
public class HtmlTemplateBase<T>:TemplateBase<T>, IViewDataContainer
{
private HtmlHelper<T> helper = null;
private ViewDataDictionary viewdata = null;
public HtmlHelper<T> Html
{
get
{
if (helper == null)
{
var writer = this.CurrentWriter; //TemplateBase.CurrentWriter
var context = new ViewContext() { RequestContext = HttpContext.Current.Request.RequestContext, Writer = writer, ViewData = this.ViewData };
helper = new HtmlHelper<T>(vcontext, this);
}
return helper;
}
}
public ViewDataDictionary ViewData
{
get
{
if (viewdata == null)
{
viewdata = new ViewDataDictionary();
viewdata.TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = string.Empty };
if (this.Model != null)
{
viewdata.Model = Model;
}
}
return viewdata;
}
set
{
viewdata = value;
}
}
public override void WriteTo(TextWriter writer, object value)
{
if (writer == null)
throw new ArgumentNullException("writer");
if (value == null) return;
//try to cast to RazorEngine IEncodedString
var encodedString = value as IEncodedString;
if (encodedString != null)
{
writer.Write(encodedString);
}
else
{
//try to cast to IHtmlString (Could be returned by Mvc Html helper methods)
var htmlString = value as IHtmlString;
if (htmlString != null) writer.Write(htmlString.ToHtmlString());
else
{
//default implementation is to convert to RazorEngine encoded string
encodedString = TemplateService.EncodedStringFactory.CreateEncodedString(value);
writer.Write(encodedString);
}
}
}
}
Then you have to use HtmlTemplateBase (modified base on https://antaris.github.io/RazorEngine/TemplateBasics.html#Extending-the-template-Syntax):
var config = new TemplateServiceConfiguration();
// You can use the #inherits directive instead (this is the fallback if no #inherits is found).
config.BaseTemplateType = typeof(HtmlTemplateBase<>);
using (var service = RazorEngineService.Create(config))
{
string template = "<h2> Welcome To Page </h2> <br/> #Html.Action(\"actionName\", \"controllerName\")";
string result = service.RunCompile(template, "htmlRawTemplate", null, null);
}
in essence, it is telling the RazorEngine to use a base template where mvc is involved, so that Html and Url helper can be used.
I'm developing a multi-tenancy MVC 4 application on which the user has some theming possibilities.
He can override every single resource (css, js, jpg, png, ect...) by adding a relative path to a theming table e.g. /Scripts/booking.js
Which tenant to use is figured out by the URL e.g. http://myapp/tenant/Booking/New this is simply the name of the connection string which should be used.
Therefore if a request is made for a specific resource I first need to check if there is an overridden version of this resource in the database and use it if found.
Now I'd like to implement the new bundling and minification features which microsoft provides in the System.Web.Optimization namespace. But I couldn't figure out how to achieve this with the files in the database.
I've prototyped my own JsMinify implementation to achieve this
public class MyJsMinify : JsMinify
{
private static byte[] GetContentFile(FileInfo filePath)
{
string fullName = filePath.FullName;
int indexOf = fullName.IndexOf("content", StringComparison.OrdinalIgnoreCase);
string substring = fullName.Substring(indexOf + 8).Replace(#"\\", "/").Replace(#"\", "/");
ThemingService themingService = ObjectFactory.GetInstance<ThemingService>();
Theming myTheming = themingService.Find(new ThemingFilter { FilePathLike = substring });
if (myTheming == null)
{
return themingService.GetContentFile(fullName);
}
return myTheming.FileData;
}
public override void Process(BundleContext context, BundleResponse response)
{
StringBuilder newContent = new StringBuilder();
foreach (FileInfo fileInfo in response.Files)
{
using (MemoryStream memoryStream = new MemoryStream(GetContentFile(fileInfo)))
{
using (StreamReader myStreamReader = new StreamReader(memoryStream, true))
{
newContent.AppendLine(myStreamReader.ReadToEnd());
}
}
}
response.Content = newContent.ToString();
base.Process(context, response);
}
}
This seems to work if I'm in Release mode but while developing I'd like to get each single script referenced independently. This is automatically done throughout the bundling and minification framework. The Resource URL's generated by the framework looks like the following
<script src="/myapp/Content/Scripts/jquery-1.9.0.js"></script>
but should look like this
<script src="/myapp/tenant/Content/Scripts/jquery-1.9.0.js"></script>
I've configured the following Routes:
routeCollection.MapRoute("Content1", "{mandator}/Content/{*filePath}", new { mandator = defaultMandator, controller = "Environment", action = "ContentFile" }, new { mandator = mandatorConstraints });
routeCollection.MapRoute("Content2", "Content/{*filePath}", new { mandator = defaultMandator, controller = "Environment", action = "ContentFile" }, new { mandator = mandatorConstraints });
The ContentFile Method looks like this
[AcceptVerbs(HttpVerbs.Get)]
[AcceptType(HttpTypes.All)]
[OutputCache(CacheProfile = "ContentFile")]
public ActionResult ContentFile(string filePath)
{
if (string.Compare(filePath, "Stylesheets/Import.css", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(CssFileArray, "Stylesheets/");
}
if (string.Compare(filePath, "Stylesheets/ImportOutlook.css", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(OutlookCssFileArray, "Stylesheets/");
}
if (string.Compare(filePath, "Scripts/OutlookAddin/Import.js", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(OutlookJsFileArray, "Scripts/");
}
return new FileContentResult(GetContentFile(filePath), MimeType(filePath));
}
Does anybody have an idea how I could achieve this?
Is there a multi-tenancy pattern to follow?
So I'm not sure I completely understand your scenario, but I believe this is what VirtualPathProviders could be used for.
We added support in the 1.1-alpha1 release, so bundles will automatically use the VirtualPathProvider registered with ASP.NET to fetch the contents of the file.
If you were to write a custom VPP that is able to always return the correct version of ~/Scripts/booking.js, everything should just work.