I'm trying to do some URL rewriting in asp.net core 2.2 but it doesn't seem to work with the query string part. I want to change any path like "finditem?txn=3" into something like "find/item?transactionid=3". As a simpler example, without a smart replacement of the transactionID, look at this code:
private static RewriteOptions GetRewriteOptions() => new RewriteOptions()
.AddRewrite(#"^bananatxn=\d$", "Download", true) // Works with bananatxn=1
.AddRewrite(#"^banana\?txn=\d$", "Download", true); // Does NOT work with banana?txn=1
Why can't the rewriter match on the question mark character? I've tested my patterns in http://regexstorm.net/tester and although the pattern seems to be correct it doesn't work. Can the rewriter in asp.net core rewrite the entire URL, including the query string, or only the part before the question mark?
I've investigate and think (but am not sure) this functionality is not available in the built-in rules provided by asp.net core. This worked for me. Definitely NOT tested thoroughly, probably gives too much importance to upper and lower case, and I'm not super familiar with all the URL components and formats.
public class RewritePathAndQuery : IRule
{
private Regex _regex;
private readonly string _replacement;
private readonly RuleResult _resultIfRewrite;
/// <param name="regex">Pattern for the path and query, excluding the initial forward slash.</param>
public RewritePathAndQuery(string regex, string replacement, bool skipRemainingRules)
{
_regex = new Regex(regex);
_replacement = replacement;
_resultIfRewrite = skipRemainingRules ? RuleResult.SkipRemainingRules : RuleResult.ContinueRules;
}
public void ApplyRule(RewriteContext context)
{
HttpRequest request = context.HttpContext.Request;
string pathExcludingInitialForwardSlash = request.Path.Value.Substring(1);
string queryStringWithLeadingQuestionCharacter = request.QueryString.Value;
string original = $"{pathExcludingInitialForwardSlash}{queryStringWithLeadingQuestionCharacter}";
string replaced = _regex.Replace(original, _replacement);
if (replaced.StartsWith('/')) { // Replacement pattern may include this character
replaced = replaced.Substring(1);
}
if (original != replaced) { // Case comparison?
string[] parts = replaced.Split('?');
request.Path = $"/{parts[0]}";
request.QueryString = new QueryString(parts.Length == 2 ? $"?{parts[1]}" : "");
context.Result = _resultIfRewrite;
}
else {
context.Result = RuleResult.ContinueRules;
}
}
}
Related
I'm using asp.net core 2.1 and I have a problem on redirect.
My URL is like:
HTTP://localhost:60695/ShowProduct/2/شال-آبی
the last parameter is in Persian.
and it throws below error:
InvalidOperationException: Invalid non-ASCII or control character in header: 0x0634
but when I change the last parameter in English like:
HTTP://localhost:60695/ShowProduct/2/scarf-blue
it works and everything is OK.
I'm using below codes for redirecting:
[HttpPost]
[Route("Login")]
public IActionResult Login(LoginViewModel login, string returnUrl)
{
if (!ModelState.IsValid)
{
ViewBag.ReturnUrl = returnUrl;
return View(login);
}
//SignIn Codes is hidden
if (Url.IsLocalUrl(returnUrl) && !string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
if (permissionService.CheckUserIsInRole(user.UserId, "Admin"))
{
return Redirect("/Admin/Dashboard");
}
ViewBag.IsSuccess = true;
return View();
}
how can I fix the problem?
General speaking, it is caused by the Redirect(returnUrl). This method will return a RedirectResult(url) and finally set the Response.Headers["Location"] as below :
Response.Headers[HeaderNames.Location] = returnUrl;
But the Headers of HTTP doesn't accept non-ASCII characters.
There're already some issues(#2678 , #4919) suggesting to encode the URL by default. But there's no such a out-of-box function yet.
A quick fix to your issue:
var host= "http://localhost:60695";
var path = "/ShowProduct/2/شال-آبی";
path=String.Join(
"/",
path.Split("/").Select(s => System.Net.WebUtility.UrlEncode(s))
);
return Redirect(host+path);
Another simpler option (works for me):
var uri = new Uri(urlStr);
return Redirect(uri.AbsoluteUri);
I use Flurl
var encoded = Flurl.Url.EncodeIllegalCharacters(url);
return base.Redirect(encoded);
This works well for absolute and relative URLs.
The option that worked for us was to use UriHelper.Encode. This method correctly works with relative and absolute URLs and also URLs containing international domain names.
In our case URLs were always absolute and redirect code looked like:
if (Uri.TryCreate(redirectUrl, UriKind.Absolute, out var uri))
{
return Redirect(UriHelper.Encode(uri));
}
You can apply URL encoding to it for storing it on response header:
string urlEncodedValue = WebUtility.UrlEncode(value);
Vice versa to decode it:
string value = WebUtility.UrlDecode(urlEncodedValue);
I have a Controller that has has a parameter of type Expression<Foo, bool> with the name query however that parameter does not show up in the generated swagger.json file. Instead a lot (>1000) parameters that have names like these show up:
Body.CanReduce
ReturnType.IsGenericMethodParameter
Type.IsGenericType
I would like to tell SwaggerGen to have my parameter just shown up as a string. If it is possible using a Filter, that would be my preferred way, but an Attribute would be fine too.
I already tried using an IOperationFilter, but it did not work as operation.Parameters does not even seem to have a paramater with the name query.
private static readonly Type _expressionType = typeof(Expression);
public void Apply(Operation operation, OperationFilterContext context)
{
foreach (var parameter in context.ApiDescription.ActionDescriptor.Parameters)
{
if(_expressionType.IsAssignableFrom(parameter.ParameterType))
{
// The parameter is found ...
var expressionParameter = operation.Parameters.FirstOrDefault(p => p.Name == parameter.Name);
if (expressionParameter != null)
Debugger.Break(); // ... but is not in the operation.Parameters collection although the >1000 mentioned above are.
}
}
}
P.S. to anyone interested: I'm using a custom ModelBinder and System.Linq.Dynamic to parse a query string to an Expression<Foo, bool>
I have a list of names in a observable collection returned from wcf service and database is oracle, I want to make full text search on the list using LINQ.
service is consumed in silverlight application.
Any suggestions pls.
How about this?
var found = thelist.Where(str => str.Contains(strToSearchFor));
or maybe this -
var found = thelist.Where(str => str.ToLower().Contains(strToSearchFor.ToLower()));
if it is not list of strings it would like like this:
var found = thelist.Where(obj => obj.strProperty.Contains(strToSearchFor));
If you need this solution to be case insensitive the solution of Hogan can be done without creatring a new string (by using the ToLower() method).
First, create an extension method:
public static class Extensions
{
public static bool Contains(this string source, string stringToMatch, StringComparison comparisonType)
{
return source.IndexOf(stringToMatch, comparisonType) >= 0;
}
}
Then you can make the Hogan solution case insensitive like this:
var found = thelist.Where(str => str.Contains(strToSearchFor, StringComparison.OrdinalIgnoreCase));
I wanted to take advantage of built-in content negotiator and just get access to decision what formatter is going to be used. I don't want to use Request.Headers.Accept and check for whether it is json or xml content type because there lot of things are involved in that decision. Is there a way I can check at controller level or override any class that tells me what formatter going to be used OR what request content type is?
thanks in advance.
You can run conneg manually:
var conneg = Configuration.Services.GetContentNegotiator();
var connegResult = conneg.Negotiate(
typeof(YOUR_TYPE), Request, Configuration.Formatters
);
And use the output whichever way you want:
//the valid media type
var mediaType = connegResult.MediaType;
//do stuff
//the relevant formatter
var formatter = connegResult.Formatter;
//do stuff
If you want to see what is going on then install a TraceWriter and you will see what the conneg does.
A TraceWriter looks something like:
public class TraceWriter : ITraceWriter {
public bool IsEnabled(string category, TraceLevel level) {
return true;
}
public void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction) {
var rec = new TraceRecord(request, category, level);
traceAction(rec);
Log(rec);
}
private void Log(TraceRecord record) {
Console.WriteLine(record.Message);
}
}
and is installed like this,
config.Services.Replace(typeof(ITraceWriter), new TraceWriter());
If you want to manually invoke conneg then you can use,
config.Services.GetContentNegotiator().Negotiate(...)
Tugberk has a blog on this. Have a look.
I've been using NH Validator for some time, mostly through ValidationDefs, but I'm still not sure about two things:
Is there any special benefit of using ValidationDef for simple/standard validations (like NotNull, MaxLength etc)?
I'm worried about the fact that those two methods throw different kinds of exceptions on validation, for example:
ValidationDef's Define.NotNullable() throws PropertyValueException
When using [NotNull] attribute, an InvalidStateException is thrown.
This makes me think mixing these two approaches isn't a good idea - it will be very difficult to handle validation exceptions consistently. Any suggestions/recommendations?
ValidationDef is probably more suitable for business-rules validation even if, having said that, I used it even for simple validation. There's more here.
What I like about ValidationDef is the fact that it has got a fluent interface.
I've been playing around with this engine for quite a while and I've put together something that works quite well for me.
I've defined an interface:
public interface IValidationEngine
{
bool IsValid(Entity entity);
IList<Validation.IBrokenRule> Validate(Entity entity);
}
Which is implemented in my validation engine:
public class ValidationEngine : Validation.IValidationEngine
{
private NHibernate.Validator.Engine.ValidatorEngine _Validator;
public ValidationEngine()
{
var vtor = new NHibernate.Validator.Engine.ValidatorEngine();
var configuration = new FluentConfiguration();
configuration
.SetDefaultValidatorMode(ValidatorMode.UseExternal)
.Register<Data.NH.Validation.User, Domain.User>()
.Register<Data.NH.Validation.Company, Domain.Company>()
.Register<Data.NH.Validation.PlanType, Domain.PlanType>();
vtor.Configure(configuration);
this._Validator = vtor;
}
public bool IsValid(DomainModel.Entity entity)
{
return (this._Validator.IsValid(entity));
}
public IList<Validation.IBrokenRule> Validate(DomainModel.Entity entity)
{
var Values = new List<Validation.IBrokenRule>();
NHibernate.Validator.Engine.InvalidValue[] values = this._Validator.Validate(entity);
if (values.Length > 0)
{
foreach (var value in values)
{
Values.Add(
new Validation.BrokenRule()
{
// Entity = value.Entity as BpReminders.Data.DomainModel.Entity,
// EntityType = value.EntityType,
EntityTypeName = value.EntityType.Name,
Message = value.Message,
PropertyName = value.PropertyName,
PropertyPath = value.PropertyPath,
// RootEntity = value.RootEntity as DomainModel.Entity,
Value = value.Value
});
}
}
return (Values);
}
}
I plug all my domain rules in there.
I bootstrap the engine at the app startup:
For<Validation.IValidationEngine>()
.Singleton()
.Use<Validation.ValidationEngine>();
Now, when I need to validate my entities before save, I just use the engine:
if (!this._ValidationEngine.IsValid(User))
{
BrokenRules = this._ValidationEngine.Validate(User);
}
and return, eventually, the collection of broken rules.