Dashed routing in ASP.NET Core MVC? - asp.net-mvc

Before Core, I used something like this in MVC:
public class HyphenatedRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["controller"] = requestContext.RouteData.Values["controller"].ToString().Replace("-", "_");
requestContext.RouteData.Values["action"] = requestContext.RouteData.Values["action"].ToString().Replace("-", "_");
return base.GetHttpHandler(requestContext);
}
}
How can I in ASP.Net Core use dashes in urls? ... like http://www.example.com/my-friendly-url ... and convert it to action my_friendly_url.
I don't want to use Attribute Routes.
Thanks

Add this convention in Startup in ConfigureServices method:
options.Conventions.Add(new DashedRoutingConvention());
Routes in UseMvc will not work. They simply will not be considered by ASP.Net itself. I have created issue on GitHub... but not sure how it will go. For now you can specify routes with attributes on methods. Convention will reuse/copy original routes and update/add new dashed path in format {controller}/{action}.
public class DashedRoutingConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
string parent = this.Convert(controller.ControllerName);
foreach (ActionModel action in controller.Actions)
{
string child = this.Convert(action.ActionName);
string template = $"{parent}/{child}";
if (this.Lookup(action.Selectors, template) == true)
continue;
List<SelectorModel> selectors = action.Selectors.Where(item => item.AttributeRouteModel?.Template == null).ToList();
if (selectors.Count > 0)
{
foreach (SelectorModel existing in selectors)
{
if (existing.AttributeRouteModel == null)
existing.AttributeRouteModel = new AttributeRouteModel();
existing.AttributeRouteModel.Template = template;
}
}
else
{
selectors = action.Selectors.Where(item => item.AttributeRouteModel?.Template != null).ToList();
foreach (SelectorModel existing in selectors)
{
SelectorModel selector = new SelectorModel(existing);
selector.AttributeRouteModel.Template = template;
if (action.Selectors.Any(item => this.Compare(item, selector)) == false)
action.Selectors.Add(selector);
}
}
}
}
private string Convert(string token)
{
if (token == null)
throw new ArgumentNullException(nameof(token));
if (token == string.Empty)
throw new ArgumentException("Failed to convert empty token.");
return Regex.Replace(token, "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])", "-$1", RegexOptions.Compiled).Trim().ToLower();
}
private bool Lookup(IEnumerable<SelectorModel> selectors, string template)
{
foreach (SelectorModel selector in selectors)
{
string current = selector.AttributeRouteModel?.Template;
if (string.Compare(current, template, StringComparison.OrdinalIgnoreCase) == 0)
return true;
}
return false;
}
private bool Compare(SelectorModel existing, SelectorModel adding)
{
if (existing.AttributeRouteModel == null && adding.AttributeRouteModel != null)
return false;
if (existing.AttributeRouteModel != null && adding.AttributeRouteModel == null)
return false;
if (existing.AttributeRouteModel != null && adding.AttributeRouteModel != null)
{
if (existing.AttributeRouteModel.Template != adding.AttributeRouteModel.Template)
return false;
if (existing.AttributeRouteModel.Order != adding.AttributeRouteModel.Order)
return false;
}
if (existing.ActionConstraints == null && adding.ActionConstraints != null)
return false;
if (existing.ActionConstraints != null && adding.ActionConstraints == null)
return false;
if (existing.ActionConstraints != null && adding.ActionConstraints != null)
{
if (existing.ActionConstraints.Count != adding.ActionConstraints.Count)
return false;
}
return true;
}
}

Related

Full RelayCommand in F#?

I am new to F#. As a learning experiment, I am rewriting a C# application in pure F#. How can the "RelayCommand" , RelayCommand, of C# be written in F# ?
Any help is most appreciated.
TIA
As requested, this is the RelayCommand I've been using in C#. :( It seems overly complicated. (There must surely be a simpler one!).
public class RelayCommand<T> : ICommand
{
private readonly WeakAction<T> _execute;
private readonly WeakFunc<T, bool> _canExecute;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = new WeakAction<T>(execute);
if (canExecute != null)
{
_canExecute = new WeakFunc<T, bool>(canExecute);
}
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (_canExecute != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
if (_canExecute.IsStatic || _canExecute.IsAlive)
{
if (parameter == null && typeof(T).IsValueType)
{
return _canExecute.Execute(default(T));
}
if (parameter == null || parameter is T)
{
return (_canExecute.Execute((T)parameter));
}
}
return false;
}
public virtual void Execute(object parameter)
{
var val = parameter;
if (CanExecute(val)
&& _execute != null
&& (_execute.IsStatic || _execute.IsAlive))
{
if (val == null)
{
if (typeof(T).IsValueType)
{
_execute.Execute(default(T));
}
else
{
// ReSharper disable ExpressionIsAlwaysNull
_execute.Execute((T)val);
// ReSharper restore ExpressionIsAlwaysNull
}
}
else
{
_execute.Execute((T)val);
}
}
}
}
public class WeakFunc<TResult>
{
private Func<TResult> _staticFunc;
/// <summary>
/// Gets or sets the <see cref="MethodInfo" /> corresponding to this WeakFunc's
/// method passed in the constructor.
/// </summary>
protected MethodInfo Method
{
get;
set;
}
/// <summary>
/// Get a value indicating whether the WeakFunc is static or not.
/// </summary>
public bool IsStatic
{
get
{
return _staticFunc != null;
}
}
public virtual string MethodName
{
get
{
if (_staticFunc != null)
{
return _staticFunc.Method.Name;
}
return Method.Name;
}
}
protected WeakReference FuncReference
{
get;
set;
}
protected WeakReference Reference
{
get;
set;
}
protected WeakFunc()
{
}
public WeakFunc(Func<TResult> func)
: this(target: func?.Target, func: func)
{
}
public WeakFunc(object target, Func<TResult> func)
{
if (func.Method.IsStatic)
{
_staticFunc = func;
if (target != null)
{
// Keep a reference to the target to control the
// WeakAction's lifetime.
Reference = new WeakReference(target);
}
return;
}
Method = func.Method;
FuncReference = new WeakReference(func.Target);
Reference = new WeakReference(target);
}
public virtual bool IsAlive
{
get
{
if (_staticFunc == null
&& Reference == null)
{
return false;
}
if (_staticFunc != null)
{
if (Reference != null)
{
return Reference.IsAlive;
}
return true;
}
return Reference.IsAlive;
}
}
public object Target
{
get
{
if (Reference == null)
{
return null;
}
return Reference.Target;
}
}
protected object FuncTarget
{
get
{
if (FuncReference == null)
{
return null;
}
return FuncReference.Target;
}
}
public TResult Execute()
{
if (_staticFunc != null)
{
return _staticFunc();
}
var funcTarget = FuncTarget;
if (IsAlive)
{
if (Method != null
&& FuncReference != null
&& funcTarget != null)
{
return (TResult)Method.Invoke(funcTarget, null);
}
}
return default(TResult);
}
public void MarkForDeletion()
{
Reference = null;
FuncReference = null;
Method = null;
_staticFunc = null;
}
}
As a newbie to F#, how can this be implemented? How does F# deal with "WeakAction"?
Thank you.

How to deal with a non existing session variable?

I am trying to check if a booking record exists, then show its details. Otherwise return to Bookparking page but else part isn't working and shows Object reference not set to an instance of an object because there is no such field with the Session[""]
Controller:
public ActionResult Viewparking()
{
if (IsUserLoggedIn(Session, Request) == false)
{
return RedirectToAction("login");
}
else
{
String id = Session["username"].ToString();
ViewBag.userid = id;
var checkbooking = db.tb_booking.Where(s => s.username == id).FirstOrDefault();
if (checkbooking != null)
{
var show = db.tb_booking.Where(e => e.username == id).FirstOrDefault();
}
else
{ //ViewBag.f = "You have no booking yet!!";
return RedirectToAction("Bookparking", "user");
}
return View();
}
}
As Gabriel noted, you have not null checked the value from the session. Code would be something like this:
public ActionResult Viewparking()
{
if (IsUserLoggedIn(Session, Request) == false)
{
return RedirectToAction("login");
}
else
{
String id = Session["username"]?.ToString();
if (id != null)
{
ViewBag.userid = id;
var checkbooking = db.tb_booking.FirstOrDefault(s => s.username == id);
if (checkbooking != null)
{ // TODO: checkbooking is currently unused, except to check if you can fetch it.
return View();
}
}
// If you reach this code, then either id is null, or the booking was not found
return RedirectToAction("Bookparking", "user");
}
}

ASP.NET Core [FromBody] vs MVC 5 binding

I got an MVC 5 application that i'm porting to asp.net Core.
In the MVC application call to controller we're made using AngularJS $resource (sending JSON) and we we're POSTing data doing :
ressource.save({ entries: vm.entries, projectId: vm.project.id }).$promise...
that will send a JSON body like:
{
entries:
[
{
// lots of fields
}
],
projectId:12
}
the MVC controller looked like this :
[HttpPost]
public JsonResult Save(List<EntryViewModel> entries, int projectId) {
// code here
}
How can I replicate the same behaviour with .NET Core since we can't have multiple [FromBody]
you cannot have multiple parameter with the FromBody attibute in an action method. If that is need, use a complex type such as a class with properties equivalent to the parameter or dynamic type like that
[HttpPost("save/{projectId}")]
public JsonResult Save(int projectId, [FromBody] dynamic entries) {
// code here
}
As pointed out in the comment, one possible solution is to unify the properties you're posting onto a single model class.
Something like the following should do the trick:
public class SaveModel
{
public List<EntryViewModel> Entries{get;set;}
public int ProjectId {get;set;}
}
Don't forget to decorate the model with the [FromBody] attribute:
[HttpPost]
public JsonResult Save([FromBody]SaveViewModel model)
{
// code here
}
Hope this helps!
It's still rough but I made a Filter to mimic the feature.
public class OldMVCFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Method != "GET")
{
var body = context.HttpContext.Request.Body;
JToken token = null;
var param = context.ActionDescriptor.Parameters;
using (var reader = new StreamReader(body))
using (var jsonReader = new JsonTextReader(reader))
{
jsonReader.CloseInput = false;
token = JToken.Load(jsonReader);
}
if (token != null)
{
var serializer = new JsonSerializer();
serializer.DefaultValueHandling = DefaultValueHandling.Populate;
serializer.FloatFormatHandling = FloatFormatHandling.DefaultValue;
foreach (var item in param)
{
JToken model = token[item.Name];
if (model == null)
{
// try to cast the full body as the current object
model = token.Root;
}
if (model != null)
{
model = this.RemoveEmptyChildren(model, item.ParameterType);
var res = model.ToObject(item.ParameterType, serializer);
context.ActionArguments[item.Name] = res;
}
}
}
}
}
private JToken RemoveEmptyChildren(JToken token, Type type)
{
var HasBaseType = type.GenericTypeArguments.Count() > 0;
List<PropertyInfo> PIList = new List<PropertyInfo>();
if (HasBaseType)
{
PIList.AddRange(type.GenericTypeArguments.FirstOrDefault().GetProperties().ToList());
}
else
{
PIList.AddRange(type.GetTypeInfo().GetProperties().ToList());
}
if (token != null)
{
if (token.Type == JTokenType.Object)
{
JObject copy = new JObject();
foreach (JProperty jProp in token.Children<JProperty>())
{
var pi = PIList.FirstOrDefault(p => p.Name == jProp.Name);
if (pi != null) // If destination type dont have this property we ignore it
{
JToken child = jProp.Value;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, pi.PropertyType);
}
if (!IsEmpty(child))
{
if (child.Type == JTokenType.Object || child.Type == JTokenType.Array)
{
// nested value has been checked, we add the object
copy.Add(jProp.Name, child);
}
else
{
if (!pi.Name.ToLowerInvariant().Contains("string"))
{
// ignore empty value when type is not string
var Val = (string)child;
if (!string.IsNullOrWhiteSpace(Val))
{
// we add the property only if it contain meningfull data
copy.Add(jProp.Name, child);
}
}
}
}
}
}
return copy;
}
else if (token.Type == JTokenType.Array)
{
JArray copy = new JArray();
foreach (JToken item in token.Children())
{
JToken child = item;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, type);
}
if (!IsEmpty(child))
{
copy.Add(child);
}
}
return copy;
}
return token;
}
return null;
}
private bool IsEmpty(JToken token)
{
return (token.Type == JTokenType.Null || token.Type == JTokenType.Undefined);
}
}

Entity framework: Db context scope and transaction

I have an MVC 5 web app which talks to my class library for db needs and the class library uses Entity Framework 6 for that.
Below are 2 methods from the class library.
Both of them are initiating a new context. How do I make 'em use only one context instead, without using class level variable?
Also, if these 2 methods were to save stuff, context.SaveChanges(), how can I wrap them both into one transaction, even if the save happens in different classes?
public int FindUnknownByName(string name, string language)
{
using (var context = new ScriptEntities())
{
int languageId = this.FindLanguage(language);
var script = context.scripts.Where(l => l.Name == name && l.Unknown == true && l.LanguageId == languageId).FirstOrDefault();
if (script != null)
{
return script.Id;
}
return 0;
}
}
public int FindLanguage(string language)
{
using (var context = new ScriptEntities())
{
var lang = context.languages.Where(l => l.Name == language).FirstOrDefault();
if (lang != null)
{
return lang.Id;
}
return 0;
}
}
Make the context a private class variable.
private ScriptEntities context = new ScriptEntities()
public int FindUnknownByName(string name, string language)
{
int languageId = this.FindLanguage(language);
var script = context.scripts.Where(l => l.Name == name && l.Unknown == true && l.LanguageId == languageId).FirstOrDefault();
if (script != null)
{
return script.Id;
}
return 0;
}
public int FindLanguage(string language)
{
var lang = context.languages.Where(l => l.Name == language).FirstOrDefault();
if (lang != null)
{
return lang.Id;
}
return 0;
}
Also implement IDisposable and dispose context.
You can use extension methods:
public int FindUnknownByName(this ScriptEntities context, string name, string language)
{
int languageId = context.FindLanguage(language);
var script = context.scripts.Where(l => l.Name == name && l.Unknown == true && l.LanguageId == languageId).FirstOrDefault();
if (script != null)
{
return script.Id;
}
return 0;.
}
public int FindLanguage(this ScriptEntities context, string language)
{
var lang = context.languages.Where(l => l.Name == language).FirstOrDefault();
if (lang != null)
{
return lang.Id;
}
return 0;
}
then:
using (var context = new ScriptEntities())
{
var language = context.FindUnknownByName('name', 'language')
}

ASP.NET MVC5 Generic Translation method for database entities

I have two methods for a database entity which gets the translation for selected language if it has one, or the default language translation.
public string GetName(int? LanguageId, int? DefaultLanguageId)
{
string retval = "";
var textInSelectedLanguage = this.CategoryTexts.Where(w => w.LanguageId == LanguageId).SingleOrDefault();
if (textInSelectedLanguage == null)
{
retval = this.CategoryTexts.Where(w => w.LanguageId == DefaultLanguageId).SingleOrDefault().Name;
}
else
{
retval = textInSelectedLanguage.Name;
}
return retval;
}
public string GetDescription(int? LanguageId, int? DefaultLanguageId)
{
string retval = "";
var textInSelectedLanguage = this.CategoryTexts.Where(w => w.LanguageId == LanguageId).SingleOrDefault();
if (textInSelectedLanguage == null)
{
retval = this.CategoryTexts.Where(w => w.LanguageId == DefaultLanguageId).SingleOrDefault().Description;
}
else
{
retval = textInSelectedLanguage.Description;
}
return retval;
}
As you can see they are very similar. If I have more properties to translate, this won't be a good implementation. The behavior is similar for the other translations.
How can reduce this code to one method?
I tried to use reflection but I didn't had any results.
Later...
I reduced my code to one method which return me the an entity instance with all the properties in the selected language or default language:
public CategoryText GetTranslation(int? DesiredLanguageId, int? DefaultLanguageId)
{
CategoryText retval = null;
var textInSelectedLanguage = this.CategoryTexts.Where(w => w.LanguageId == DesiredLanguageId).SingleOrDefault();
if (textInSelectedLanguage == null)
{
retval = this.CategoryTexts.Where(w => w.LanguageId == DefaultLanguageId).SingleOrDefault();
}
else
{
retval = textInSelectedLanguage;
}
return retval;
}
I think this method can be easily made generic by trying to find a way to replace my CategoryTexts Dbset with any other DbSet database entity. How can I do this?
Assuming you have an appropriate base class you could write it:-
public T GetTranslation<T>(DbSet<T> set, int? DesiredLanguageId, int? DefaultLanguageId)
where T:SomeBaseClassThatHasPropertyLanguageId
{
return
set.SingleOrDefault(w => w.LanguageId == DesiredLanguageId) ??
set.SingleOrDefault(w => w.LanguageId == DefaultLanguageId);
}
I somehow solved this.
I have an Internationalization class which with the following method:
public static T GetDbEntityTranslation<T>(ITranslatable Entity)
{
return (T)Entity.GetTranslation<T>(GetDefaultLanguage().Id, GetChosenLanguage().Id);
}
The ITranslatable interface:
public interface ITranslatable
{
T GetTranslation<T>(int? DesiredLanguageId, int? DefaultLanguageId);
}
My category class:
public partial class Category : ITranslatable
{
private LSEntities db = new LSEntities();
public T GetTranslation<T>(int? DesiredLanguageId, int? DefaultLanguageId)
{
CategoryText retval = null;
retval = db.CategoryTexts.Where(w => w.LanguageId == DesiredLanguageId && w.CategoryId == this.Id).SingleOrDefault()
?? this.CategoryTexts.Where(w => w.LanguageId == DefaultLanguageId && w.CategoryId == this.Id).SingleOrDefault()
?? this.CategoryTexts.Where(w => w.CategoryId == this.Id).FirstOrDefault();
if (retval == null)
throw new Exception("No translation found for this object");
return (T)(object)retval;
}
}
The problem was that I need to get my translation based on my category ID which is stored in Category class. My translations are searched in CategoryTexts property.

Resources