Passing AppSettings to external javascript file the MVC way? - asp.net

I have settings in AppSettings (web.config) and I need to pass them to an external javascript file.
In ASP.NET I would think of an ASHX handler to write the javascript file to the response replacing placeholders with the settings values.
Is there a better way to do it in ASP.NET MVC? Thank you.

You could send them via a JsonResult?
In your JS, you'd have a request which sends a GET/POST request to a particular action (let's call it GetAppSetting(), and the corresponding value is returned in the response.
For security reasons, I would restrict what can be requested though...
public JsonResult GetAppSetting(string id)
{
//You could check what's been requested here if you want to make sure you're only returning information that you may not wish to send.
string appSetting = AppSettings[id];
if(string.IsNullOrEmpty(appSetting) == false)
{
return Json(appSetting, JsonRequestBehavior.AllowGet);
}
//Handle non-existent settings here...
throw new Exception("This setting does not exist");
}
Alternatively, it has been suggested by Chris Marisic in the comments that you may want to absolutely limit this to just a specific set of key/values for developer reasons. Therefore, here is a quick example of that...
public JsonResult GetAppSettings()
{
var appSettings = new Dictionary<string, string>();
appSettings.Add("myAppSetting1", AppSettings["myAppSetting1"]);
appSettings.Add("myAppSetting2", AppSettings["myAppSetting2"]);
appSettings.Add("myAppSetting3", AppSettings["myAppSetting3"]);
return Json(appSettings, JsonRequestBehavior.AllowGet);
}
Note the JsonRequestBehavior.AllowGet in the JsonResults (MVC 2 only). This is because, by default, ASP.NET MVC 2 will not allow GET requests on actions which return a JsonResult. You can circumvent this by adding the JsonRequestBehaviour, but I should probably mention that you should consider doing a post request in order to retrieve this information, and remove this behaviour in your action.

Related

How to Overload HttpPost Web API Method Based Json Datatype Properties

I am asked to implement a REST Web API to a specific route, where either of two different Json Datatypes may be posted.
This results in the following exception being thrown:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
Is there an Attribute that can be placed on the Web Methods, referencing Properties of the Json payloads so as to disambiguate the two possible Datatypes?
This was covered here but I'll add a little bit.
It's not good API design to do that and goes against Swagger / OpenAPI specifications to do what you're asking.
The only way to do this with the same HTTP method (POST in your case) is to have one action that takes in both models. Check which one isn't null to then route to the correct method to handle that logic.
If you can get away with using a different HTTP verb you could technically do that and have two separate action methods (like POST and PUT), but you wouldn't be using them "correctly" and based on your question and need, I doubt you can do that anyway.
You can read the request body as a string and then try to decide which type to deserialize in:
[HttpPost]
[Route("api/mypath")]
public async Task<IActionResult> MyMethod()
{
request.Body.Position = 0;
var reader = new StreamReader(request.Body, Encoding.UTF8);
var body = await reader.ReadToEndAsync();
if(body.Contains("A))
{
var A = JsonConvert.DeserializeObject<A>(body);
}
else{
var B = JsonConvert.DeserializeObject<B>(body);
}
}
And add a middleware to enable request buffering:
app.Use(next => context => {
context.Request.EnableBuffering();
return next(context);
});
You can read more about it here

Show a message after redirecting after a successful POST request without using TempData

I am using the Post-Redirect-Get pattern.
In my asp.net core MVC web app, this is what happens:
User submits a form via POST which adds an item to db.
Controller adds the new item and redirects with 302/303 to "/Home/Index/xxxx", where xxxx is the id of the item.
The new request (/Home/Index/xxxx) is served by the controller, and it displays the item. And the item url in the address bar is something the user can copy and share.
At step 3 above, I would like to show the user a message saying "Item was successfully added".
This is my code (without the success message):
public async Task<IActionResult> Index(string id)
{
ItemView itemView = null;
if (string.IsNullOrEmpty(id))
itemView = new ItemView(); // Create an empty item.
else
itemView = await itemService.GetItemAsync(id);
return View(itemView);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(ItemView itemView)
{
string id = await itemService.AddItemAsync(itemView);
return RedirectToAction("Index", "Home", new { id = id });
}
There are few ways to do this that I found in other answers on stackoverflow.
Redirect to "/Home/Index/xxxx?success=true". When action sees a success=true param, it can display the success message. But I don't want to use an extra param because I would like users to be able to just copy the url from the address bar and share it. And I don't want them sharing the url that has success param, because then everyone who clicks on the shared link will see the message "Item was successfully added".
This post suggests using TempData, which is a good solution. I think that would need me to enable sticky behavior on the server, which I would like to avoid if possible.
I can probably use referrer url to determine if the request came after a form submission, and in that case I can show the message.
The original answer by "snoopy" did point me in the right direction. But for some unknown reason, that answer no longer exists, so I am posting the answer myself in the hope it would benefit someone in future.
ASP .NET Core 1.1 and higher supports Cookie based Tempdata provider called CookieTempDataProvider. Link to Microsoft Docs.
This is similar to Session based Tempdata, but no data is stored on the server side. The response from the server set's a cookie in the browser with the data you want to store. The next request from the browser will include this cookie. The framework automatically parses this and populates this in TempData, which the controller can use. Once the controller reads this data, then the CookieTempDataProvider automatically adds the appropriate headers in the response to clear this cookie.
In your Startup class's ConfigureServices method, you need to register CookieTempDataProvider:
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
To store some data in cookie based temp data, you simple set the value like this in your controller:
TempData["key"] = "value";
To read the data in your controller, you read it like this:
string value = TempData["key"];
if (value != null)
{
// Do something with the the value.
}
The check for non-null tells you if that key exists in TempData or not. Note that you can also check using .ContainsKey() method, but that is not counted as a read. The data (& the cookie) will not be cleared unless you read it. For example this will not clear the data:
if (TempData.ContainsKey("key"))
{
// Do something without actually reading the value of TempData["key"].
}

Alternative to Server.Transfer in ASP.NET Core

I am migrating an ASP.NET application to ASP.NET Core and they have some calls to HttpServerUtility.Transfer(string path). However, HttpServerUtility does not exist in ASP.NET Core.
Is there an alternative that I can use? Or is Response.Redirect the only option I have?
I want to maintain the same behaviour as the old application as much as possible since there is a difference in between Server.Transfer and Response.Redirect.
I see some options for you, depending on your case:
Returning another View: So just the HTML. See answer of Muqeet Khan
Returning another method of the same controller: This allows also the execution of the business logic of the other action. Just write something like return MyOtherAction("foo", "bar").
Returning an action of another controller: See the answer of Ron C. I am a bit in troubles with this solution since it omits the whole middleware which contains like 90% of the logic of ASP.NET Core (like security, cookies, compression, ...).
Routing style middleware: Adding a middleware similar to what routing does. In this case your decision logic needs to be evaluated there.
Late re-running of the middleware stack: You essentially need to re-run a big part of the stack. I believe it is possible, but have not seen a solution yet. I have seen a presentation of Damian Edwards (PM for ASP.NET Core) where he hosted ASP.NET Core without Kestrel/TCPIP usage just for rendering HTML locally in a browser. That you could do. But that is a lot of overload.
A word of advice: Transfer is dead ;). Differences like that is the reason for ASP.NET Core existence and performance improvements. That is bad for migration but good for the overall platform.
You are correct. Server.Transfer and Server.Redirect are quite different. Server.Transfer executes a new page and returns it's results to the browser but does not inform the browser that it returned a different page. So in such a case the browser url will show the original url requested but the contents will come from some other page. This is quite different than doing a Server.Redirect which will instruct the browser to request the new page. In such a case the url displayed in the browser will change to show the new url.
To do the equivalent of a Server.Transfer in Asp.Net Core, you need to update the Request.Path and Request.QueryString properties to point to the url you want to transfer to and you need to instantiate the controller that handles that url and call it's action method. I have provided full code below to illustrate this.
page1.html
<html>
<body>
<h1>Page 1</h1>
</body>
</html>
page2.html
<html>
<body>
<h1>Page 2</h1>
</body>
</html>
ExampleTransferController.cs
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace App.Web.Controllers {
public class ExampleTransferController: Controller {
public ExampleTransferController() {
}
[Route("/example-transfer/page1")]
public IActionResult Page1() {
bool condition = true;
if(condition) {
//Store the original url in the HttpContext items
//so that it's available to the app.
string originalUrl = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.Path}{HttpContext.Request.QueryString}";
HttpContext.Items.Add("OriginalUrl", originalUrl);
//Modify the request to indicate the url we want to transfer to
string newPath = "/example-transfer/page2";
string newQueryString = "";
HttpContext.Request.Path = newPath;
HttpContext.Request.QueryString = new QueryString(newQueryString);
//Now call the action method for that new url
//Note that instantiating the controller for the new action method
//isn't necessary if the action method is on the same controller as
//the action method for the original request but
//I do it here just for illustration since often the action method you
//may want to call will be on a different controller.
var controller = new ExampleTransferController();
controller.ControllerContext = new ControllerContext(this.ControllerContext);
return controller.Page2();
}
return View();
}
[Route("/example-transfer/page2")]
public IActionResult Page2() {
string originalUrl = HttpContext.Items["OriginalUrl"] as string;
bool requestWasTransfered = (originalUrl != null);
return View();
}
}
}
Placing the original url in HttpContext.Items["OriginalUrl"] isn't strictly necessary but doing so makes it easy for the end page to know if it's responding to a transfer and if so what the original url was.
I can see this is a fairly old thread. I don't know when URL Rewriting was added to .Net Core but the answer is to rewrite the URL in the middleware, it's not a redirect, does not return to the server, does not change the url in the browser address bar, but does change the route.
resources:
https://weblog.west-wind.com/posts/2020/Mar/13/Back-to-Basics-Rewriting-a-URL-in-ASPNET-Core
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?view=aspnetcore-5.0
I believe you are looking for a "named view" return in MVC. Like so,
[HttpPost]
public ActionResult Index(string Name)
{
ViewBag.Message = "Some message";
//Like Server.Transfer() in Asp.Net WebForm
return View("MyIndex");
}
The above will return that particular view. If you have a condition that governs the view details you can do that too.
I know that this is a very old question, but if someone uses Razor Pages and is looking to a Server.Transfer alternative (or a way to return a different view depending on a business rule), you can use partial views.
In this example, my viewmodel has a property called "UseAlternateView":
public class TestModel : PageModel
{
public bool UseAlternateView { get; set; }
public void OnGet()
{
// Here goes code that can set UseAlternateView=true in certain conditions
}
}
In my Razor View, I renderize a diferent partial view depending of the value of the UseAlternateView property:
#model MyProject.Pages.TestModel
#if (Model.UseAlternateView)
{
await Html.RenderPartialAsync("_View1", Model);
}
else
{
await Html.RenderPartialAsync("_View2", Model);
}
The partial views (files "_View1.cshtml" and "_View2.cshtml"), contain code like this:
#model MyProject.Pages.TestModel
<div>
Here goes page content, including forms with binding to Model properties
when necessary
</div>
Obs.: when using partial views like this, you cannot use #Region, so you may need to look for an anternative for inserting scripts and styles in the correct place on the master page.

Elegant way of avoiding ASP.NET MVC routing errors

I have a route:
routes.MapRoute("ResetPasswordConfirm", "reset-password-confirm", new { controller = "Membership", action = "ResetPasswordConfirm" });
and the code
public ActionResult ResetPasswordConfirm(int userid, string key)
{
// ...
}
in my application. So that i have url to be executed like this:
http://localhost/reset-password-confirm?userid=1&key=bla_bla_something
That is absolutely okay, until someone decides to go to
http://localhost/reset-password-confirm
...and look what will happen. ASP.NET will generate predictable error:
The parameters dictionary contains a null entry for parameter 'userid' of non-nullable type 'System.Int32'...
It could be done also by a search robot trying to grab all the possible urls. It's okay, but generates a lot of errors during usage of application in the wild. I want to avoid that, but feel uncomfortable with writing a stub for every possible case for such kind of errors.
Is there any elegant way of doing that? Thanks.
Another way is to handle global errors, just set <customErrors mode="On"></customErrors> on your web.config and create an Error.cshtml on your Shared view folder. MVC3 templates actually include that page.
On the other hand, if you want to be more specific, you should try Action Filters, that's a cool way to handle errors.
[HandleError(View = "YourErrorView", ExceptionType=typeof(NullReferenceException))]
public ActionResult ResetPasswordConfirm(int? userid, string key)
{
if (!userid.HasValue)
throw new NullReferenceException();
// ...
}
Use nullables for your parameters, i.e.:
public ActionResult ResetPasswordConfirm(int? userid, string key)

Delete link with jquery confirm in Asp.Net MVC 2 application

I have a table with records that has delete links. Basically I followed the NerdDinner tutorial for that part in MVC. But I don't want to have the confirm view, I'd rather have a confirm dialog, and then if confirmed let the HttPost action method delete the record directly.
I have something working, but being new to both MVC and jQuery, I'm not sure if I'm doing it right.
Here's the jQuery:
$(function () {
$('.delete').click(function () {
var answer = confirm('Do you want to delete this record?');
if (answer) {
$.post(this.href, function () {
window.location.reload(); //Callback
});
return false;
}
return false;
});
});
And the delete actionlink:
<%: Html.ActionLink("Delete", "Delete", new { id = user.UserID }, new { #class = "delete" })%>
And finally, the delete action method:
[HttpPost]
public ActionResult Delete(int id)
{
_db = new UsersEntities();
try
{
//First delete the dependency relationship:
DeleteUserVisits(id);
//Then delete the user object itself:
DeleteUser(id);
}
catch (Exception)
{
return View("NotFound");
}
return RedirectToAction("Index");
}
Now, I have a few questions about this:
I found the function for making the link POST instead of GET on the internet: `$.post(this.href); return false; And I'm not sure what the "return false" part does. I tried to modify it for my needs with a callback and so on, and kept the return part without knowing what it's for.
Secondly, the action method has the HttpPost attribute, but I have also read somewhere that you can use a delete verb. Should you? And what about the RedirectToAction? The purpose of that was originally to refresh the page, but that didn't work, so I added the window.location.reload instead in a callback in the jQuery. Any thoughts?
The Delete action method calls a couple of helper methods because it seems with the Entity Framework that I use for data, I can't just delete an record if it has relationship dependencies. I had to first delete the relationships and then the "User" record itself. That works fine, but I would have thought it would be possible to just delete a record and all the relationships would be automatically deleted...?
I know you're not supposed to just delete with links directly because crawlers etc could accidentally be deleting records. But with the jQuery, are there any security issues with this? Crawlers couldn't do that when there is a jQuery check in between, right?
Any clarifications would be greatly appreciated!
The return false statement serves
the same purpose as
e.preventDefault() in this case.
By returning false JavaScript is preventing
the browser from executing the link's
default click handler.
The DELETE verb is for RESTful web
services, it is most likely not what
you want in this situation because
not all browsers fully implement it.
Read more about them here: Creating
a RESTful Web Service Using ASP.Net
MVC
The deletion rules for child
entities depend on the entity
OnDelete/OnUpdate rules in the
context definition and also on
foreign key constraints in the
database. You can learn more about
foreign key constraints here.
A crawler will not be able to
activate your delete link in this
fashion because you have specified
the delete action be issued via a
POST. If the delete were a GET then
it would be a concern. Still, it's
not wise to keep links that directly
modify your content on front-facing
pages that do not require
authentication as a human could most
certainly exploit the process.
There you go, with full code :
http://haacked.com/archive/2009/01/30/delete-link-with-downlevel-support.aspx
Or just the html helper :
public static string DeleteLink(this HtmlHelper html, string linkText, string routeName, object routeValues) {
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
string url = urlHelper.RouteUrl(routeName, routeValues);
string format = #"<form method=""post"" action=""{0}"" class=""delete-link"">
<input type=""submit"" value=""{1}"" />
{2}
</form>";
string form = string.Format(format, html.AttributeEncode(url), html.AttributeEncode(linkText), html.AntiForgeryToken());
return form + html.RouteLink(linkText, routeName, routeValues, new { #class = "delete-link", style = "display:none;" });
}
For your questions :
When you do a return false at the end of a javascript click handler on a link, the browser executes the javascript but doesn't follow the link, because of the return false.
For (maximum) cross browser compatibility, you shouldn't use the DELETE verb.
I don't know EF well enough, but doesn't it provide mapping configuration for cascading delete ?
HTTP GET should only be used to get information, for example, provide parameters for a search. Crawlers usually don't care about javascript, so they will blindly follow you links. If it is http://mysite/delete/everything, then, bad for you, if you hadn't the [HttpPost] on you controller action
If you also want to throw the jQuery dialog to the party, this Ricardo Covo post does a good job

Resources