http:www.site1.com/?sid=555
I want to be able to have the sid parameter and value persist whether a form is posted or a link is clicked.
If the user navigates to a view that implements paging, then the other parameters in the querystring should be added after the sid.
http:www.site1.com/?sid=555&page=3
How can I accomplish this in Asp.Net Mvc 2?
[Edit]
The url I mentioned on top would be the entry point of the application, so the sid will be included in the link.
Within the application links like:
<%= Html.ActionLink("Detail", "Detail", new { controller = "User",
id = item.UserId })%>
should go to:
http:www.site1.com/user/detail/3?sid=555
This question is different than what Dave mentions, as the querystring parameter is persisting throughout the site.
Firstly, I'd say if the value needs to be persisted throughout the session then you should store it in Session and check that its still valid on each action call. This can be done through a custom action attribute you add to the controller / actions required. If the value is required then when the value is checked you can re-drect to a login page or similar if not present or its expired.
Anyway, that said I thought I would have a crack at getting it working. My first thought would be to create a custom action filter attribute which took the value of the querstring and stored it in session in OnActionExecuting and then OnResultExecuted would add the key back to the querystring. But as QueryString in Request is a read-only collection you can't do it directly.
So, whats now available to you?
Option #1 - Add it to all calls to Html.ActionLink() manually
or ...
Option #2 - Override a version of ActionLink which automatically adds the value for you. This can be achived like so. I wouldn't recommend doing this though.
Start off with the custom attribute.
public class PersistQueryStringAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var sid = filterContext.RequestContext.HttpContext.Request.QueryString["sid"];
if (!string.IsNullOrEmpty(sid))
{
filterContext.RequestContext.HttpContext.Session["sid"] = sid;
}
base.OnActionExecuting(filterContext);
}
}
All this does is check the request querystring for the required key and if its available add it into the session.
Then you override ActionLink extention method to one of your own which adds the value in.
public static class HtmlHelperExtensions
{
public static MvcHtmlString ActionLink<TModel>(this HtmlHelper<TModel> helper, string text, string action, string controller, object routeValues)
{
var routeValueDictionary = new RouteValueDictionary(routeValues);
if (helper.ViewContext.RequestContext.HttpContext.Session["sid"] != null)
{
routeValueDictionary.Add("sid", helper.ViewContext.RequestContext.HttpContext.Session["sid"]);
}
return helper.ActionLink(text, action, controller, routeValueDictionary, null);
}
}
On each of the action which is going to be called apply the attribute (or apply it to the controller), eg:
[PersistQueryString]
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
Note
As the query value gets put into session it will be applied for the life of the session. If you want to check that the value is there and the same each request you will need to do some checking in the attribute overridden method.
Finally
I've purely done this as a "can it be done" exercise. I would highly recommend against it.
Possible Duplicate:
How do you persist querystring values in asp.net mvc?
I agree with the accepted answer to the question linked above. Querystring parameters are not designed for data persistence. If a setting (i.e. sid=555) is intended to persist through a session, use Session state or your Model to save that data for use across requests.
Related
I'm very confused about the design of my RESTful services!
If I was doing this using vanilla MVC3/4 then I would simply have action methods marked [HTTPGet] etc. and I could have multiple Get's per controller. I this way I would organise controllers by their "meta group".
I've looked at the Web API MVC4 template and it gives me the automatic translation from an Http GET to the Getxxx() method - but this implies a single Get per controller and organising controllers by object, rather than function...which seems to make some sense.
I see many posts on adding named routes - but this seems to break the natural model of Get, Post, Put, Delete. If I do that - then aren't I (in essence) just going back to vanilla MVC4?
Is there any impact on having lots of controllers?
Am I thinking
about this correctly?
Shortly,
Is there any impact on having lots of controllers?
No
Am I thinking about this correctly?
Generally yes.
Default WebAPI/MVc template uses routing that relays on prefixes and naming GetXXX, PostXX.
RouteTable.Routes.MapRoute(
"WithActionApi",
"api/{controller}/{action}/{id}"
);
But you can create your own custom routing with action names instead. Then you uses in URL name of your action method and as you've wrote Attributes to set HTTP Verbs like [HttpGet]
RouteTable.Routes.MapRoute(
"DefaultApi",
"api/{controller}/{id}",
new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);
[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
return new string[] { "byCategory1", "byCategory2" };
}
I'm upgrading a custom solution where I can dynamically register and unregister Web Api controllers to use the new attribute routing mechanism. However, it seems to recent update to RTM break my solution.
My solution exposes a couple of Web Api controllers for administration purposes. These are registered using the new HttpConfigurationExtensions.MapHttpAttributeRoutes method call.
The solution also allows Web Api controllers to be hosted in third-party assemblies and registered dynamically. At this stage, calling HttpConfigurationExtensions.MapHttAttributeRoutes a second time once the third-party controller is loaded would raise an exception. Therefore, my solution uses reflection to inspect the RoutePrefix and Route attributes and register corresponding routes on the HttpConfiguration object.
Unfortunately, calling the Web Api results in the following error:
"No HTTP resource was found that matches the request URI".
Here is a simple controller that I want to use:
[RoutePrefix("api/ze")]
public sealed class ZeController : ApiController
{
[HttpGet]
[Route("one")]
public string GetOne()
{
return "One";
}
[HttpGet]
[Route("two")]
public string GetTwo()
{
return "Two";
}
[HttpPost]
[Route("one")]
public string SetOne(string value)
{
return String.Empty;
}
}
Here is the first solution I tried:
configuration.Routes.MapHttpRoute("ZeApi", "api/ze/{action}");
Here is the second solution I tried:
var type = typeof(ZeController);
var routeMembers = type.GetMethods().Where(m => m.IsPublic);
foreach (MethodInfo method in routeMembers)
{
var routeAttribute = method.GetCustomAttributes(false).OfType<RouteAttribute>().FirstOrDefault();
if (routeAttribute != null)
{
string controllerName = type.Name.Substring(0, type.Name.LastIndexOf("Controller"));
string routeTemplate = string.Join("/", "api/Ze", routeAttribute.Template);
configuration.Routes.MapHttpRoute(method.Name, routeTemplate);
}
}
I also have tried a third solution, whereby I create custom classes that implement IHttpRoute and trying to register them with the configuration to no avail.
Is it possible to use legacy-style route mapping based upon the information contained in the new routing attributes ?
Update
I have installed my controller in a Web Application in order to troubleshoot the routing selection process with the Web Api Route Debugger. Here is the result of the screenshot:
As you can see, the correct action seems to be selected, but I still get a 404 error.
Update2
After further analysis, and per Kiran Challa's comment below, it seems that the design of Web Api prevents mixing attribute routing and conventional routing, and that what I want to do is not possible using this approach.
I have created a custom attribute [RouteEx] that serves the same purpose of the Web Api [Route] attribute, and now my code works perfectly.
I guess, since this is not possible using the conventional attribute routing, none of the answers on this question could legitimately be consisered valid. So I'm not nominating an answer just yet.
You shouldn't be required to use reflection and inspect the attribute-routing based attributes yourself. Attribute routing uses existing Web API features to get list of controllers to scan through.
Question: Before the switch to attribute routing, how were you loading these assemblies having the
controllers?
If you were doing this by IAssembliesResolver service, then this solution should work even with attribute routing and you should not be needing to do anything extra.
Regarding your Update: are you calling MapHttpAttributeRoutes?
I am using the Asp.Net Web Api. I would like to be able to filter out certain fields on the response objects based on the connected clients access rights.
Example:
class Foo
{
[AccessFilter("Uberlord")]
string Wibble { get; set; }
string Wobble { get; set; }
}
When returning data the filed Wibble should only be returned if the current users context can satisfy the value of "Uberlord".
There are three avenues that I am exploring but I have not got a working solution:
A custom WebApi MediaTypeFormatter.
A custom json.net IContractResolver.
Some sort of AOP wrapper for controllers that manipulates the response object
My issue with these are:
The custom formatter does not feel like the right place to do it but might be the only option.
The custom json serializer would not have access to the current context so I would have to work that out.
With the first two options you would require specific implementations for each response format, json, xml, some custom format, etc. This would mean that if another response type is supported then a custom formatter / serializer is required to prevent sensitive data leaking.
The AOP controller wrapper would require a lot of reflection.
An additional bonus would be to strip out values from the fields on an inbound request object using the same mechanism.
Have I missed an obvious hook? Has this been solved by another way?
It was actually a lot simpler than I first thought. What I did not realise is that the DelegatingHandler can be used to manipulate the response as well as the request in the Web Api Pipeline.
Lifecycle of an ASP.NET Web API Message
Delegating Handler
Delegating handlers are an extensibility point in the message pipeline allowing you to massage the Request before passing it on to the rest of the pipeline. The response message on its way back has to pass through the Delegating Handler as well, so any response can also be monitored/filtered/updated at this extensibility point.
Delegating Handlers if required, can bypass the rest of the pipeline too and send back and Http Response themselves.
Example
Here is an example implementation of a DelegatingHandler that can either manipulate the response object or replace it altogether.
public class ResponseDataFilterHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
//Manipulate content here
var content = response.Content as ObjectContent;
if (content != null && content.Value != null)
{
((SomeObject)content.Value).SomeProperty = null;
}
//Or replace the content
response.Content = new ObjectContent(typeof(object), new object(), new JsonMediaTypeFormatter());
return response;
});
}
}
Microsoft article on how to implement a delegating handler and add it to the pipeline.HTTP Message Handlers in ASP.NET Web API
I have a similar question in the works over here: ASP.NET WebAPI Conditional Serialization based on User Role
A proposed solution that I came up with is to have my ApiController inherit from a BaseApiController which overrides the Initalize function to set the appropriate formatter based on the user's role. I haven't decided if I will go this way yet, but perhaps it will work for you.
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
// If the user is in a sensitive-data access role
controllerContext.Configuration.Formatters.Add(/*My Formatter*/);
// Otherwise use the default ones added in global app_start that defaults to remove sensitive data
}
i am creating a partialview with a controller action like:
public ActionResult GetPostsByUser(string userName)
{
where userName is part of the URL:
www.example.com/User/toddM
toddM being the userName
First off.. am i going about this the right way?? if i make it a querystring ?userName=toddM it works.. but i need it to read from the URL. Again, this is a partialview . thanks!
Your Url sample is not "well formed" as it miss one of the controller/action.
In fact, as per the default route created by the MVC project template you should have
"{controller}/{action}/{id}", // URL with parameters
An URL like www.example.com/Post/User/toddM would perfectly fits within the default route and so I think will work without any problem.
This one would be your action in an hypothetical PostController
public ActionResult User( string id )
{
//id will contain toddM
}
I'm using TempDate["Message"] to show little update banners as the user does things on my site like this:
[AcceptVerbs(HttpVerbs.Post), Authorize(Roles = "Admins")]
public ActionResult Delete(int id)
{
_Repo.DeletePage(id); // soft-delete
TempData["Message"] = "Page deleted!";
return RedirectToAction("Revisions", "Page", new { id = id });
}
Then in my master page I have this:
<%-- message box (show it only if it contains a message) --%>
<% string Message = (TempData["Message"] ?? ViewData["Message"]) as string;
if(!string.IsNullOrEmpty(Message)){
%>
<div id="message"><%:Message %></div>
<% }
TempData["Message"] = null; ViewData["Message"] = null; %>
I hit both TempData and ViewData because I read somewhere that TempData should be used for redirects and ViewData should be used otherwise.
The issue is: often the message won't show up right away. Sometimes it takes a click or two to different parts of the site for the message to show up. It's very strange.
Any ideas?
You should verify all places where you use TempData["Message"] in your code. Corresponds to ASP.NET MVC does browser refresh make TempData useless? you can read TempData["Message"] only once (see also http://forums.asp.net/p/1528070/3694325.aspx). During the first uage of TempData["Message"], the TempData["Message"] will be deleted from the internal TempDataDictionary.
Probably it would be better to use TempData["Message"] only inside of Revisions action of the Page controller and not inside of master page or inside a View.
TempData is not intended to pass data to views, hence the name ViewData for that purpose. In fact, I can't think of a reason to use TempData from within a view definition at all...
One very common usage of TempData is the passing of information between controller actions when you do a redirect (the Revisions action in your example above, for instance, would be able to make use of your TempData["Message"] variable).
This is common practice in the PRG means of coding MVC interactions (Post-Redirect-Get) since you often need to pass information from the initial target action when doing the Redirect to the Get. An example of how this might be useful in a Get is below where I often just default to a new viewmodel UNLESS there is one already passed from a redirect in TempData:
public ActionResult System() {
SystemAdminVM model = (SystemAdminVM)TempData["screenData"] ?? new SystemAdminVM();
One more thing; I see you explicitly clearing your TempData and ViewData dictionary entries in your view. You don't need to do that as by that point they are at the end of their life spans anyway...
Happy coding!
Your app's behavior is the one you'd expect if you're using TempData where you should be using ViewData.
You want to double-check that you're storing your status feedbacks in TempData only when the controller does a re-direct. Otherwise, you should use ViewData.
This smells like you need a couple of unit tests to confirm the behavior you're seeing. Try writing up a couple using this example as a starting point:
http://weblogs.asp.net/leftslipper/archive/2008/04/13/mvc-unit-testing-controller-actions-that-use-tempdata.aspx
If you have configured multiple worker process for your application, but session state mode is "InProc", then you can't use default TempData implementation, as session state becomes unusable. (see ASP.NET session state and multiple worker processes)
You could try to use MvcFutures CookieTempDataProvider instead.