How to clear Session after View Rendering - c#

This initially looked like a game, but bit after bit I ended up carrying this issue for quite a long time. Here is my situation. I fire Notifications from my Domain Model.
These notifications are just objects containing a title and a description that I store in a collection in order to render them at the top of the pages of my website. However I'm having trouble to find the appropriate "session" mechanism with MVC.
I started by using HttpContext.Items to store all my session data, but I found out that it wasn't suitable for Redirection Scenarios - when I redirect the user to an other Action Method. Indeed a new HttpContext is created and the Items object is lost.
Consequently I tried storing my session stuff in HttpContext.Session but the issue I have now is that there is no appropriate time to clear the Session (I don't want to carry on the notifications from one request to the other). OnActionExecuted and OnResultExecuted seem to run before the View is Rendered.
Here is how I display the notifications in my Layout page :
#foreach(var notification in ISession.Notifications)
{
#Html.Partial("_NotificationPartial", new Mvc.Models.NotificationViewModel(notification))
}
ISession is mapped to a store (HttpContext.Items / HttpContext.Session) in my IOC container.
Do you have any workaround idea ?

Try using the TempDataDictionary. It is included on the Controller base class as the TempData property. It is meant to persist data from one request to another. It is then cleared out automatically.
In the action method:
TempData["Notifications"] = new List<Notification>()
In the view:
#{
if(TempData["Notifications"] != null)
{
var notifications = TempData["Notifications"] as List<Notification>
}
}

Related

How to properly load heavy collection?

I'm learning ASP.NET Core and I have some doubts about the loading of an heavy collection of records, let me explain better.
What I'm trying to do
In my application, after the login execution, the user will redirected to the Dashboard Home View. The Dashboard is the place that contains all the functions for an user. The Dashboard Controller have also other Views like:
Home
Analysis
Performance
Now each View need to display to the user a Table which contains a list of Products, at the bottom of this Table there is the content of the View.
Problem
First problem: is the Table redundancy code, which I solved creating a _PartialView that contains the html of the Table that contains the products to display. Coming from c# + WPF I used the same logic of UserControl, so this is a good solution for me.
Second problem: the products to display inside the Table, these products are downloaded from an API, now as I said before, these records must be always displayed in the products Table (which is available in different View using the _PartialView). Imagine that every time the user click on a Dashboard item (which load a Dashboard View), the Dashboard Controller will call this method:
public async Task<List<Products>> GetProducts(string date)
{
var client = new RestClient(Url);
var request = new RestRequest("product/get_products/{date}", Method.GET);
request.AddUrlSegment("date", date);
var cancellationTokenSource = new CancellationTokenSource();
var response = await client.ExecuteTaskAsync(request, cancellationTokenSource.Token);
List<Products> products = JsonConvert.DeserializeObject<List<Product>>(response.Content);
return products;
}
For me, this is not a really good practice because each time the _PartialView will call this method and reload the data, so I need to store somehow this data (a temp store). How can I store these records to the user session without reload each time the _PartialView being called?
Between, I have some doubts about the API method:
Should I place all the API calls inside the Service folder? Repository folder? Or Controller folder?
Folder tree
View <- Folder
Dashboard <- Folder
Home
Analysis
Performance
_ProductsTable
The View Home, Analysis, Performance load _ProductsTable in the following way:
#await Html.PartialAsync("_LeftSidebar")
Use view components. They're essentially self-contained modules of functionality that return views, which you can embed in other views, without main view or action having to know about any of it.
First, create a directory call ViewComponents. Inside add new class, like ProductsViewComponent. Then, you'll want something like:
public class ProductsViewComponent : ViewComponent
{
private readonly HttpClient _client;
public ProductsViewComponent(HttpClient client)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task<IViewComponentResult> InvokeAsync(string date)
{
using (var response = await _client.GetAsync($"/"product/get_products/{date}"))
{
response.EnsureSuccessStatusCode();
var products = await response.Content.ReadAsAsync<List<Product>>();
return View(products);
}
}
}
Then, create the view, Views\Shared\Components\Products\Default.cshtml. Inside, add the HTML to render your list of products. Finally, where you want the product table to appear add:
#await Component.InvokeAsync("Products", new { date = myDate })
The above code uses HttpClient rather than RestClient, since honestly, it's completely unnecessary at this point to have a separate library for making HTTP calls. HttpClient is built-in and has been extended with functionality in Core to make this much easier, such as the ReadAsAsync method used above, which transparently deserializes your JSON response into the generic type argument. Additionally, you now have things like IHttpClientFactory which ensures that you have properly scoped HttpClient instances. As a result, the above code also assumes adding something like the following to your Startup.cs:
services.AddHttpClient<ProductsViewComponent>(c =>
{
c.BaseAddress = new Uri('https://api.myservice.com');
// add default headers and such if you need them
});
You can also then use the Polly integration to setup automatic retries, circuit breakers, etc., allowing you handle all sorts of API scenarios such as temporarily unavailable, rate limits, etc. See the full documentation for both IHttpClientFactory and its Polly integration for more info.
Lastly, if this is a scenario where you don't need realtime data, you can also inject an instance of IDistributedCache into your view component and add logic to set the result of your API call in that, and retrieve it from there first, before making the call again, allowing you to significantly reduce the load both on your app and the API (especially if do have something where rate limits apply).

ASP.NET Core MVC - Adding element permanently to Dictionary in Action

I'm working on a ASP.NET Core MVC web app. I have a Model that includes a Dictionary. In one Action I'm adding a new element to it. Then I have other actions supposed to use the object from the Dictionary that was just added. But as it turns out - the dictionary is empty after the first action finished executing. Is there a way to fix it, so that the object is added permanently to the dictionary?
Update:
Well, the object I need to store is basically a virtual medical slide with a Deep Zoom tile generator. The flow is as follows: user click on the link to open the slide -> the ViewSlide Action creates the slide object -> then the OpenSeadragon viewer on the corresponding view sends requests to get XML metadata and JPEG tiles (256x256) on various Deep Zoom levels (based on mouse cursor position). So there's going to be a lot of requests for the tiles and I'm looking for a way to optimize the time needed to create them.
Here's a code snippet of the said actions:
[Route("[controller]/{slug}")]
public IActionResult ViewSlide(string slug)
{
try
{
var currentSlide = slideSet.Get(slug);
return View(currentSlide);
}
catch (Exception)
{
return RedirectToAction("Index");
}
}
public Slide Get(string slideUrl)
{
if (Slides.ContainsKey(slideUrl))
return Slides[slideUrl];
var pathToSlide = FilePaths[slideUrl];
Slides[slideUrl] = new Slide(pathToSlide);
return Slides[slideUrl];
}
[Produces("application/xml")]
[Route("[controller]/{slug}.dzi")]
public string Dzi(string slug)
{
try
{
return slideSet.Get(slug).DeepZoomGenerator.GetDziMetadataString(DEEPZOOM_FORMAT);
}
catch (Exception)
{
RedirectToAction("Index");
return "";
}
}
If you want to add the item permanently you can store it in:
Session (will not work in a web farm)
Cookie
Database
File
Here is how to store it in session:
// Place something in session
System.Web.HttpContext.Current.Session["whatever"] = value;
// Read from session
var whatever = System.Web.HttpContext.Current.Session["whatever"];
MVC also provides TempData which is basically a session which lives during the lifecycle of the trip on the server.
Depending on how you want to use this data, you have different options:
You can store it in the Session, Cookie, or TempData, if it's tied to the client, and no one else will need it. How long do you want to store the data? Cookies can be cleared, and you don't want to hold too much data in the Session either for a long time.
If the data does not belong to specific users, you can use a repository (e.g. singleton dictionary / database / HttpCache), but the first two needs to be cleaned regularly, while the HttpCache is not guaranteed to hold the data until it's requested.
And you could also rethink this concept, and stay stateless. This also makes it easier to scale your application horizontally, as well as adding HTTP cache, or even reverse proxy.
So basically it depends on what kind of data would you like to persist between action calls.

Maintain the model lifetime in MVC application

I am new to MVC and I have very simple problem.
When user login to my application I need to create a specific object (model) for the user for eg UserObject.
This object is unique to current logged in user and should only be disposed when user click on logout.
I don’t know how to maintain the lifetime of the object. As if I create object in Action method of controller class then as soon as the request is finished I lose the reference of the object.
How this should have been done?
The lifetime of your models are only going to be as long as the request. So each time the user goes to another page or refreshes, the MVC framework is going to instantiate a new controller (and model within). Otherwise your server would have a ton of static objects floating around in memory which would use up a lot of resources and wouldn't scale.
In order to manage state, you are going to need to use other methods such as sessions/cookies and a database.
So let's say the user logs in via /User/Login. This routes the request to an action named UserController.Login().
Inside this action, it instantiates a UserModel.
public ActionResult Login(string username, string password) {
var userModel = new UserModel();
if (userModel.Authenticate(username, password)) {
// Setup your session to maintain state
Session["username"] = username;
} else {
return View("Login");
}
return View("LoginComplete");
}
You might want the user model to actually create the session, but I've shown it here for clarity.
The user model authenticates the user, and then you create a session just like you would in a traditional non-MVC site.
Then in subsequent requests, you will want to authorize the user, and use any session data you have to retrieve state information.
public ActionResult SuperSecretPlace() {
var userModel = new UserModel();
string username = Session["username"]
var user = userModel.GetUserByUsername(username);
if (user == null) throw new HttpException(401, "User is not authorized.");
return View("SuperSecretPlace", user);
}
In the action above, the UserModel might do something like query a database to retrieve the user's data so you can pass it in to the corresponding view.
If you want to make life easier, you might want to just use .NET's built in forms authentication:
http://www.codeproject.com/Articles/578374/AplusBeginner-splusTutorialplusonplusCustomplusF
For more info about the lifecycle of MVC:
http://www.dotnet-tricks.com/Tutorial/mvc/TbR0041112-Asp.net-MVC-Request-Life-Cycle.html
http://www.asp.net/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5-application
Actually what you are trying to achieve is passing model from controller to controller which is not possible. When an action is executed the context of the model object is disposed at the view and it can cannot be passed from controller to controller. You have to create a new object repopulate it and use it to achieve the goal in different controller.If you need the data to be persisted you can use sessions but still you need to create an object of the model in every controller.
The following image is for your reference as to see what to use when passing data between model-view-controller. Please feel free to ask if you need more information on this.
As opposed to the other aswers I would not use session as it has quite some disadvantages (scalability, pessimistic concurrency which blocks concurrent calls, app pool recycling...). Why you should not use session is documented in a lot of places like here or here.
Instead, I would store it in a cookie.
However, be sure to not store confidential or sensitive data. Whatever you use (cookies or session), it can be tampered with or stolen. If you are dealing with sensitive information, you need other solutions. Read also more about secure cookie solution here.

MVC Razor Hidden input and passing values

I am pretty sure I am doing something wrong here. I have been developing a web app using MVC and Razor and I never thought of using the form element. Now so much has already been done with master pages and sub pages that it means restructuring most of our code in order to use form element and the would result in multiple form elements on a page.
That aside, in Asp.Net if I wanted to access any control in the C# code behind I could just give it an ID="SomeID" and a RUNAT="SERVER". Then in my code behind I could set its value and properties.
When I do this in Razor, I use lines like:
<input id="hiddenPostBack" runat="server" type="hidden" />
Why can't I access this in the controller? I want to detect a postback and set the value to false if it is the first time the page loads, and if not, then set the value to true. Then based on this, I will read it either server side or client side and do something.
My real question is, how do I "do something" both server side and client side given that I don't have a form element. I was under the impression that if I wanted to pass values from client to server and back, the easiest way to do this is with a hidden input. But I am just not getting how to accomplish this with MVC3 and razor.
A move from WebForms to MVC requires a complete sea-change in logic and brain processes. You're no longer interacting with the 'form' both server-side and client-side (and in fact even with WebForms you weren't interacting client-side). You've probably just mixed up a bit of thinking there, in that with WebForms and RUNAT="SERVER" you were merely interacting with the building of the Web page.
MVC is somewhat similar in that you have server-side code in constructing the model (the data you need to build what your user will see), but once you have built the HTML you need to appreciate that the link between the server and the user no longer exists. They have a page of HTML, that's it.
So the HTML you are building is read-only. You pass the model through to the Razor page, which will build HTML appropriate to that model.
If you want to have a hidden element which sets true or false depending on whether this is the first view or not you need a bool in your model, and set it to True in the Action if it's in response to a follow up. This could be done by having different actions depending on whether the request is [HttpGet] or [HttpPost] (if that's appropriate for how you set up your form: a GET request for the first visit and a POST request if submitting a form).
Alternatively the model could be set to True when it's created (which will be the first time you visit the page), but after you check the value as being True or False (since a bool defaults to False when it's instantiated). Then using:
#Html.HiddenFor(x => x.HiddenPostBack)
in your form, which will put a hidden True. When the form is posted back to your server the model will now have that value set to True.
It's hard to give much more advice than that as your question isn't specific as to why you want to do this. It's perhaps vital that you read a good book on moving to MVC from WebForms, such as Steve Sanderson's Pro ASP.NET MVC.
If you are using Razor, you cannot access the field directly, but you can manage its value.
The idea is that the first Microsoft approach drive the developers away from Web Development and make it easy for Desktop programmers (for example) to make web applications.
Meanwhile, the web developers, did not understand this tricky strange way of ASP.NET.
Actually this hidden input is rendered on client-side, and the ASP has no access to it (it never had). However, in time you will see its a piratical way and you may rely on it, when you get use with it. The web development differs from the Desktop or Mobile.
The model is your logical unit, and the hidden field (and the whole view page) is just a representative view of the data. So you can dedicate your work on the application or domain logic and the view simply just serves it to the consumer - which means you need no detailed access and "brainstorming" functionality in the view.
The controller actually does work you need for manage the hidden or general setup. The model serves specific logical unit properties and functionality and the view just renders it to the end user, simply said. Read more about MVC.
Model
public class MyClassModel
{
public int Id { get; set; }
public string Name { get; set; }
public string MyPropertyForHidden { get; set; }
}
This is the controller aciton
public ActionResult MyPageView()
{
MyClassModel model = new MyClassModel(); // Single entity, strongly-typed
// IList model = new List<MyClassModel>(); // or List, strongly-typed
// ViewBag.MyHiddenInputValue = "Something to pass"; // ...or using ViewBag
return View(model);
}
The view is below
//This will make a Model property of the View to be of MyClassModel
#model MyNamespace.Models.MyClassModel // strongly-typed view
// #model IList<MyNamespace.Models.MyClassModel> // list, strongly-typed view
// ... Some Other Code ...
#using(Html.BeginForm()) // Creates <form>
{
// Renders hidden field for your model property (strongly-typed)
// The field rendered to server your model property (Address, Phone, etc.)
Html.HiddenFor(model => Model.MyPropertyForHidden);
// For list you may use foreach on Model
// foreach(var item in Model) or foreach(MyClassModel item in Model)
}
// ... Some Other Code ...
The view with ViewBag:
// ... Some Other Code ...
#using(Html.BeginForm()) // Creates <form>
{
Html.Hidden(
"HiddenName",
ViewBag.MyHiddenInputValue,
new { #class = "hiddencss", maxlength = 255 /*, etc... */ }
);
}
// ... Some Other Code ...
We are using Html Helper to render the Hidden field or we could write it by hand - <input name=".." id=".." value="ViewBag.MyHiddenInputValue"> also.
The ViewBag is some sort of data carrier to the view. It does not restrict you with model - you can place whatever you like.
As you may have already figured, Asp.Net MVC is a different paradigm than Asp.Net (webforms). Accessing form elements between the server and client take a different approach in Asp.Net MVC.
You can google more reading material on this on the web. For now, I would suggest using Ajax to get or post data to the server. You can still employ input type="hidden", but initialize it with a value from the ViewData or for Razor, ViewBag.
For example, your controller may look like this:
public ActionResult Index()
{
ViewBag.MyInitialValue = true;
return View();
}
In your view, you can have an input elemet that is initialized by the value in your ViewBag:
<input type="hidden" name="myHiddenInput" id="myHiddenInput" value="#ViewBag.MyInitialValue" />
Then you can pass data between the client and server via ajax. For example, using jQuery:
$.get('GetMyNewValue?oldValue=' + $('#myHiddenInput').val(), function (e) {
// blah
});
You can alternatively use $.ajax, $.getJSON, $.post depending on your requirement.
First of all ASP.NET MVC does not work the same way WebForms does. You don't have the whole runat="server" thing. MVC does not offer the abstraction layer that WebForms offered. Probabaly you should try to understand what controllers and actions are and then you should look at model binding. Any beginner level tutorial about MVC shows how you can pass data between the client and the server.
You are doing it wrong since you try to map WebForms in the MVC application.
There are no server side controlls in MVC. Only the View and the
Controller on the back-end. You send the data from server to the client by
means of initialization of the View with your model.
This is happening on the HTTP GET request to your resource.
[HttpGet]
public ActionResult Home()
{
var model = new HomeModel { Greeatings = "Hi" };
return View(model);
}
You send data from client to server by means of posting data to
server. To make that happen, you create a form inside your view and
[HttpPost] handler in your controller.
// View
#using (Html.BeginForm()) {
#Html.TextBoxFor(m => m.Name)
#Html.TextBoxFor(m => m.Password)
}
// Controller
[HttpPost]
public ActionResult Home(LoginModel model)
{
// do auth.. and stuff
return Redirect();
}

MVC Routes - How to get a URL?

In my current project we have a notification system. When an oject is added to another objects collection, an email is sent to those who are subscibed to the parent object. This happens on the object layer and not in the View or Controller.
Here's the problem:
Although we can say who created what with what information in the email, we cannot embed links to those objects in the email because in the object layer there is no access to a UrlHelper. To construct a UrlHelper you need a RequestContext, which again does not exist on the object layer.
Question:
I want to make a helper class to create the url's for me. How can I create an object that will generate these urls without a request context? Is it possible?
The problem is compounded by the fact that you don't want a relative URL in an email, you want an absolute email so you need to hard-code the domain too because there is no request to grab it from.
Another factor is that emails can outlive the current site structure by months or years so you need a kind of permalink, and thus a way to associate multiple Urls with a single action (additional routes). This latter issue is also a factor in SEO where you don't want to leave any page behind.
For now a static method on your controller UrlToActionX(params) sitting next to the method ActionX seems like the simplest workaround. All it does is the appropriate string.Format(...) on the id's of the strongly-typed parameters to generate the permanent Url. Add a static domain on the front, or a domain from the user object (since you know which domain they visit when they come to your site) and you have your email link.
It's not ideal but at least you now have only one place to maintain the Url generation.
IMHO: When it comes to permanent links to a changing web site sometimes it's better to rely on "configuration over convention". :-)
I'm not aware of a way to do this, you MUST have access to the routes at the very least to make your own helper. Unless your business objects know about the registered routes, you can't get away from doing some hard-coding.
Here is how you might limit the hard-coding of urls though...
Code in a url with all the relevant bits in your object's methods..
class Event
{
public void SendEmail()
{
var url = string.Format("http://myurl.com/r/Event?eventId={0}", EventId);
//send emails...
}
}
Note the /r/Event piece of the url. This would be a map to a RController that would be responsible for taking arbitrary, made-up links and sending a 301 Permanent Redirect and going through the route engine to create a real url using the current routes. This way you are only hard-coding a utility controller url and not to the ever evolving controller actions of your real pages.
class RController : Controller
{
public ActionResult Event(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Details", "Event", new { eventId = eventId });
return null;
}
public ActionResult Register(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Register", "Event", new { eventId = eventId });
return null;
}
}
It just feels a bit better than hard-coding a bunch of different controllers/actions that you might decide to rename later. Think of it as your own little TinyUrl like service.
You could define an interface with a method that takes whatever information is necessary to create a URL (object ids or whatever) and returns a URL. Write an implementation of that interface that uses the UrlHelper to do this work, and then supply this to your object layer (ideally with an IoC container).
You could use:
VirtualPathUtility.ToAbsolute(string.Format("~/r/Event?eventId={0}", id))
to resolve the url. Still not nice though.

Categories

Resources