Is it possible to add a route to the RouteCollection in MVC? - sql-server

I couldn't think of a reasonable title for this post.
The problem I'm having is that I have an SQL database attached to my MVC website. Within the website I have a news/blog system that I have worked on which stores the data in the database and pulls the information on request.
The problem is the routing. I currently have it set up to pull the information about the routing of each individual page as this:
var newsr = new NewsResources();
foreach (var item in newsr.GetAllNewsItems())
{
item.Title = item.Title.Replace(' ', '-').ToLower();
routes.MapRoute(item.Title, "News/" + item.Title,
new {controller = "News", action = "Post", id = item.ID});}
When I add a new news item however, this doesn't go into the routing system which is proving to be a right pain. I've had a google search for dynamically adding url routing but I can't seem to find a solution.
What I want to know is, is it possible to add a page to the routing system via the page controller once I have saved the post into the database?

I do not think you need to Do a For Each loop across all your POst item and add routes for that. You may do it like this
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("SingleItem", "News/{title}",
new { controller = "Items", action = "PostFromTitle" });
// and the generic route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
This will route ../News/SomeTitle request to your PostFromTitle action method of NewsController. Read the title there and get the post from there.
public ActionResult PostFromTitle(string title)
{
var post=repo.GetPostFromTitle(title);
return View("~/Views/News/Post.cshtml",post);
}

It may be better to an action that takes a title as a parameter.
routes.MapRoute("NewsItem", "News/{title}",
new { controller = "News", action = "ShowNews" }

Related

Persist url query data in reactjs

I am using react and in some pages i am transferring data from url but the problem is when I want that data to persist if url changes until there is any other query or parameter in url.
How can i achieve that?
This is my url snapshot
I want the query data i.e idx=1 to persist if there is any change in url like this
Currently when I link to salesfunnel page I am doing this
`
componentDidMount() {
var url = this.props.location.query;
this.setState({
selid : parseInt(url.idx, 10)
})
}
`
and in that case i lost query.I want that the query should remain somewhere with me if i get it once and the data should only change when there is certain change in query of url for eg: if idx=2 from idx=1.
How can i achieve that in react?
Any help much appreciated!
Thanks :)
Can you not conditionally set the selid e.g.:
componentDidMount() {
var url = this.props.location.query;
if (url.idx) {
// Only update `selid` when `idx` is a query param
this.setState({
selid : parseInt(url.idx, 10)
});
}
}
This way you'll persist the value across page changes until the user lands on a new page that sets a new idx.

AngularJS hash url and MVC routing

I am facing issue in navigating to URL. My default page set to Login and my application URL is http://localhost:12345/#/.
This works well but there are two ways the user can login to application
Direct through the application.
Getting username and password trough query string.
When application is logging through Query String the url is like http://localhost:12345?auth=123654654656564/#/.
I would like to remove auth value from the URL. I tried to map the routing but it doesn't work.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Account", action = "Login"}
);
And also i tried to create one more action result that will return the view
routes.MapRoute(
name: "ActualDefault",
url: "{controller}/{action}",
defaults: new { controller = "Account", action = "LoginQuery" }
);
Controller:
public ActionResult Login()
{
if (Request.QueryString.Count > 0 && Request.QueryString != null)
{
//validating
return RedirectToAction("LoginQuery", "Account");
}
else
{
return View();
}
}
public ActionResult LoginQuery()
{
return View("Index");
}
The above code removes query string but the URL will be http://localhost:12345/Account/LoginQuery/#/.
I just need the URL like http://localhost:12345/#/.
Logging in via Query String
I would be negligent not to point out that this is an extremely bad practice. You should always use HTTP POST when logging into an application and send the user secrets in the body of the post, not the query string.
See
Handling Form Edit and Post Scenarios
Submit Form with Parameters in ASP.NET MVC
BUILDING ASP.NET MVC FORMS WITH RAZOR
Note that you can also create forms in plain HTML (or via angularjs) to call an MVC action method, or you can make an HTTP POST via JavaScript or some other programming language to do the same thing.
Query string values are completely ignored by MVC routing. But you can make a custom route use query string values.
public class LoginViaQueryStringRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var path = httpContext.Request.Path;
if (!string.IsNullOrEmpty(path))
{
// Don't handle URLs that have a path /controller/action
return null;
}
var queryString = httpContext.Request.QueryString;
if (!queryString.HasKeys())
{
// Don't handle the route if there is no query string.
return null;
}
if (!queryString.AllKeys.Contains("username") && !queryString.AllKeys.Contains("password"))
{
// Don't handle the case where controller and action are missing.
return null;
}
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values["controller"] = "Account";
routeData.Values["action"] = "LoginQuery";
routeData.Values["username"] = queryString["username"];
routeData.Values["password"] = queryString["password"];
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
Usage
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new LoginViaQueryStringRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
This route will now match http://localhost:12345/?username=foo&password=bar and send it to your LoginQuery action method.
Logging in via http://localhost:12345/#/
It is unclear how you expect this to work. Since everything after the hash tag are generally not sent to the server from the browser, http://localhost:12345/#/ is equivalent to http://localhost:12345/. So, you are effectively saying "I want my home page to be the login page".
In a typical MVC application, you would setup an AuthorizeAttribute on the home page to redirect the user to the login page. After the user logs in, they would be redirected back to the home page (or usually whatever secured page they initially requested).
[Authorize]
public ActionResult Index()
{
return View();
}
If you want all of your application secured, you can register the AuthorizeAttribute globally and use AllowAnonymousAttribute on your public action methods (such as the login and register pages).
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AuthorizeAttribute());
filters.Add(new HandleErrorAttribute());
}
}
And your login action methods:
[AllowAnonymous]
public ActionResult Login()
{
//...
}
[AllowAnonymous]
[HttpPost]
public ActionResult Login(LoginModel model)
{
//...
}
[AllowAnonymous]
public ActionResult LoginQuery(string username, string password)
{
//...
}
But then, that is a typical MVC-only application.
If you are using Angular to make a SPA, then this could be a very different story. Namely, you would probably switch views on the client side without doing an HTTP 302 redirect to the login form (perhaps it would be a popup - who knows). The point is, without any details of how the client is setup to communicate with MVC, it is not possible to give you any useful advice on setting up MVC for your client beyond how you would typically setup MVC to work in a multi-page application.
NOTE: I can tell you that your routing is misconfigured. The Default and ActualDefault definitions cannot exist in the same route configuration because the first match always wins, therefore the first one will run and the other one will never run. Both of the route URL definitions will match any URL that is 0, 1, or 2 segments in length, so whichever you have first in the route table will match and the other one will be an unreachable execution path.

Restangular use save()

I'm playing with Restangular and I want to make a call to .save() to update or create an entity. From Restangular github page I can see that it is possible to update or create a new account. But if there are no accounts on the server I get method does not exists. (firstAccount is undefined)
Restangular.all('accounts').getList().then(function(accounts) {
var firstAccount = accounts[0];
firstAccount.title = "New title"
// PUT /accounts/123. Save will do POST or PUT accordingly
firstAccount.save();
});
My question is how do I make firstAccount a restangular object that will go to the correct url (POST /accounts) when I call firstAccount.save() if there are no accounts in the response?
If you are trying to create a new element, you may restangularize it:
var account = {
title: 'New Title'
};
var restangularAccount = Restangular.restangularizeElement(null, account, 'accounts');
restangularAccount.save();
That will do an HTTP POST to /accounts

AngularJS web API not working with strings

At the moment what I am trying to do is to return a list of strings from my web API, but I was getting this error 'No 'Access-Control-Allow-Origin' header is present on the requested resource.'
After struggling for a while I have realised it's because it will only allow me to return a list of integers and not a list of strings.
Here are the two methods (one of which would obviously be commented out).
public IEnumerable<string> Get(int userId)
{
IEnumerable<string> listOfFormGroups = new List<string>() { "Form Group 1", "Form Group 2", "Form Group 3" };
return listOfFormGroups;
}
public IEnumerable<int> Get(int id)
{
IEnumerable<int> listOfRoles = new List<int>() { 1, 2, 3 };
return listOfRoles;
}
The top method throws the error, but the second method returns fine.
my angular service
this.get = function (userId) {
return $http.get(toApiUrl('formgroup/') + userId);
}
my angular controller calling the service,
var promiseGet = formGroupService.get(1);
promiseGet.then(function (result) {
$scope.FormGroups = result.data
},
function (errorResult) {
$log.error('Unable to get users form groups. An error has occurred. :', errorResult);
});
If you look at the default WebApiConfig, it looks like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I believe the integer version is working because you called the param id as stated in the config. The string one you have is trying to map to id, seeing userId as the only parameter and not mapping it correctly. I believe that is your error.
Change your method to look like this:
public IEnumerable<string> Get(int id)
{
IEnumerable<string> listOfFormGroups = new List<string>() { "Form Group 1", "Form Group 2", "Form Group 3" };
return listOfFormGroups;
}
peinearydevelopment's answer may be correct, but there is another interesting part of the question. The No 'Access-Control-Allow-Origin' message is a CORS error message (a cross origin request). In general, a script is limited to making AJAX requests to the same URL origin that it came from.
My guess that part of the problem is coming from the fact that toApiUrl() creates a URL that is of a different domain than the script came from.
There are ways around this, but it requires changes on the server side. See the documentation on how to do this.
It does't have any problem. You can call it by having below url.
I have already tested it and its working fine.
http://localhost:52257/api/temp/Get?userId=3
// please use your domain or localhost....
So in your case you can go with,
return $http.get(toApiUrl('formgroup/Get?userId=3'));

Backbone.js set URL parameters in model & use it with fetch

I would like to fetch model from specific url with parameter:
url: server/somecontroller/id/?type=gift
Simple working way is:
collection.fetch({ data: { type: 'gift'} });
But I want to set it in model:
...
if(id){
App.coupon = new AffiliatesApp.Coupon({id: id});
} else {
App.coupon = new AffiliatesApp.Coupon({id: 'somecontroller'}, {type: 'gift'});
}
App.coupon.fetch();
How can I achieve it?
The easiest way to achieve this is to override Backbone's url method on the Coupon model with one defined by you. For example you can do :
Affiliates.Coupon = Backbone.Model.extend({
urlRoot : "server/somecontroller/",
url : function(){
var url = this.urlRoot + this.id;
if(this.get("type")){
url = url + "/?type=" + this.get("type");
}
return url;
}
});
This solution is easy to implement but has a drawback: the generated URL will be used for every action that syncs to the server (fetch,save,..).
If you need to have a finer control over the generation of the URL depending on what action you are doing you will need to override Backbone's Sync method for your model.
It can be done by overriding the fetch method in model to use some custom data. Using CoffeeScript it could look like this:
class AffiliatesApp.Coupon extends Backbone.Model
fetch: ->
super(data: { type: #get('type') })
Note that this example will ignore any attributes passed to coupon.fetch(), however it can be easily adjusted for any override logic.

Resources