MVC3 with hundred of thousands layouts/templates - asp.net-mvc-3

I have an application where each user can choose a custom layout. The layouts can be different and it's not just css styles but html as well.
I know that mvc would cache the layout, but having so many layouts I doubt it would fit in cache. So what would it be better to save templates in DB or on the disk?
FYI: DB that I'm using is MongoDB.

I would save the layouts on disk because at the moment I don't see any advantage in a database (unless you do). But one thing that is worth mentioning is that you can create a class derived from OutputCacheAttribute and have your saved result depend on the layout you're using.
Does the layout depend on user? You could use the VaryByCustom property to have it vary by user.
EDIT
Are your users allowed to change layouts dinamically? If yes, you should also have a guid associated to your users change it each time the layouts change so you return on your VaryByCustom method:
return string.Format("User-{0}-{1}", user.Id, user.LayoutUpdateGuid);
See the meaning of this? This way, when a user changes the layouts, they will see their pages updated immediately.
How to apply the VaryByCustom attribute in your situation
In your action method, you may use:
[OutputCache(Duration = 3600, VaryByCustom = "UserLayouts")]
public ActionResult Details(string param)
{
// Returning the view
}
Then, in your VaryByCustom method in your Global.asax.cs file:
protected override string VaryByCustom(string custom)
{
switch (custom)
{
case "UserLayouts":
//// Here you fetch your user details so you can return a unique
//// string for each user and "publishing cycle"
//// Also, I strongly suggest you cache this user object and expire it
//// whenever the user is changed (e.g. when the LayoutUpdateGuid is
//// changed) so you achieve maximum speed and not defeat the purpose
//// of using output cache.
return string.Format("User-{0}-{1}", user.Id, user.LayoutUpdateGuid);
break;
}
}
The missing piece
The missing piece here is that you need to store a value that I called LayoutUpdateGuid (I'm sure you'll find a better name) and change that value whenever a user changes his layouts => this will lead to a different string being returned by the VaryByCustom(string) method in the Global.asasx.cs which in turn will force your action method to run again and return the result with the updated layout.
Makes sense to you?
Note: I can't test the specific code I wrote here, but I am sure (apart from typos) it is correct.

Related

EF Core 2.1 trying to include primary key field in INSERT query when adding to DbContext and saving

In an ASP .Net Core 2.1 Web API (with a MySQL database and using Pomelo), when I add a new entity to the database in one of my controller actions, if the entity that is received by the API from the consuming client has a value in the primary key, it appears as though EF Core is trying to add the primary key instead of allowing the database to give it a new value.
So... in the database, I have a table called person which has an integer field called id which is set to PRIMARY KEY and AUTO-INCREMENT.
Model:
public partial class Person
{
public int? Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(entity =>
{
entity.ToTable("person");
entity.HasKey(e => e.Id);
entity.Property(e => e.Id)
.HasColumnName("id")
.HasColumnType("int(11)");
entity.Property(e => e.Name)
.HasColumnName("name")
.HasColumnType("varchar(45)");
entity.Property(e => e.Surname)
.HasColumnName("surname")
.HasColumnType("varchar(45)");
}
}
Controller Action
// POST: api/Person
[HttpPost]
public async Task<IActionResult> AddPerson([FromBody]Person person)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
_context.Person.Add(person);
await _context.SaveChangesAsync();
return CreatedAtAction("GetPerson", new { id = person.Id }, person);
}
If I don't specifically clear the Id of the person before trying to insert it into the database (i.e. person.Id = null) then I get an exception complaining about duplicate primary key. Is this normal EF Core behavior? Or am I doing something wrong?
Frankly, yes, you are doing something wrong. For a whole host of reasons, you should never ever save an instance created from user input (i.e. the Person instance being passed into your action and created from the request body of the post) directly to your database. One such reason is that it causes havoc with ORMs like EF, which employ entity tracking to optimize queries.
Simply, this Person instance here is untracked - EF knows nothing about it. You then use Add to add it to your context, which signals EF to start tracking it as a new thing. When you later save, EF, then dutifully issues an insert statement, but since an id is included in that insert, you get a primary key conflict. What you wanted instead was for EF to do an update, but it doesn't know it should.
There's ways you can technically fix this. For example, you could use Attach rather than Add. That merely blindly tells EF that this is something it should track, without necessarily communicating that it should do anything with it. If you make any modifications to this instance after it is tracked, EF will change its change to "modified" and you'll end up with an update statement being issued when you save. However, if you're not making any changes, but just saving it directly, you'll also need to explicitly set it's state to "modified" or EF will essentially do nothing. The nice thing is that if you change the state on an untracked entity, then EF automatically attaches it to track said state, so you you don't need to do Attach manually. Long and short, you can clear the exception merely by replacing your Add line with:
_context.Entry(person).State = EntityState.Modified;
However, that then will cause a problem if you try to add a new person entirely. A bigger issue you have here is that you have one action doing double duty. According to REST, a POST is not replayable and should only be made to resources which or idempotent. Put more simply, you POST only to a resource like /api/person (rather than something like /api/person/1 and every time you do so a new person should be created. For an update, you should make a request to that actual resource, i.e /api/person/1 and the HTTP verb should be PUT, instead. The same PUT request to the same resource will always have the same result, which is the case for an update to a particular resource.
Theory aside, the simple point is that you should have two actions:
[HttpPost("")]
public async Task<IActionResult> AddPerson([FromBody]Person person)
[HttpPut("{id}")]
public async Task<IActionResult> UpdatePerson(int id, [FromBody]Person person)
Finally, even with all this, saving the person param directly puts too much trust in the user, when doing an update. There might be any number of properties an end-user should not be able to modify with an update (such as something like a "created" date, for example), but they can when you do this. In some ways worse, even if the user is not being malicious, you're still relying on them to post all the data for that entity. For example, if you did have a created date property, but the user doesn't post that with their update (honestly, why would you post a created date along with a request to update a resource), then it will have the effect of clearing that property out. If there's a default, it will be set back to that, and if not, you may actually get an exception on saving, if the column is NOT NULL.
Long and short, it's not a good idea. Instead, use a view model, DTO, or similar. That class should contain only properties you want to allow a user to modify or even to affect on create in the first place. Then, for the case of an update, you pull the resource fresh from the database, and map over the values from your param instance onto that. Finally, you save the version from the database back to the database. This ensures 1) the user cannot modify anything you do not explicitly allow, 2) the user only needs to post things they actually care about modifying, and 3) the entity will be properly tracked and EF will issue an update statement correctly on save.

User specific content in TYPO3 and caching

How to show not cached fe_user data in TYPO3? According to Prevent to Cache Login Information.
The ViewHelper sets $GLOBALS['TSFE']->no_cache = 1 if the user logged in. Is there a better way? Because not the whole page should not be cached, only some parts of it.
Unfortunately this is not possible.
The best way is, you render the not cached fe_user data with a AJAX called eID or TypeNum and the whole page is completly cached.
like this one:
http://www.typo3-tutorials.org/cms/typo3-und-ajax-wie-geht-das.html
your example code disabled cache for the complete page. but you only need to disable cache for the part where you display the user specific data. As you can except any part from caching you need to select whether to cache only
one content element (especially for plugins this is standard behaviour: just declare your plugin as uncachable in your ext_localconf.php)
one column (make sure you use a COA_INT (or other uncached object) in your typoscript)
one viewhelper (make your viewhelper uncachable [1] or use the v:render.uncache() VH from EXT:vhs)
[1]
as a viewhelper is derived from AbstractConditionViewHelper, which uses the Compilable Interface, which caches the result, the compile() method from AbstractConditionViewHelper must be rewritten and return the constant
\TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler::SHOULD_GENERATE_VIEWHELPER_INVOCATION
like this:
public function compile(
$argumentsVariableName,
$renderChildrenClosureVariableName,
&$initializationPhpCode,
\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $syntaxTreeNode,
\TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler $templateCompiler
) {
parent::compile(
$argumentsVariableName,
$renderChildrenClosureVariableName,
$initializationPhpCode,
$syntaxTreeNode,
$templateCompiler
);
return \TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler::SHOULD_GENERATE_VIEWHELPER_INVOCATION;
}

Flush cache on per user basis

I'm using https://github.com/filipw/AspNetWebApi-OutputCache to add easy caching to my web-api project and I have an action that look something like this:
[HttpGet]
[CacheOutput(ClientTimeSpan = 86400, ServerTimeSpan = 86400)]
public List<Things> GetThings()
{
return service.GetThings();
}
Now things are a combination of a list of things that apply to everybody along with user-defined things that are created by a user and accessible only to that user. So I want the cache here to be tied to a specific user. I don't want user Bob getting a list of things that included things that are specific to Sally. So I created my own key generator, inheriting from DefaultCacheKeyGenerator that will append the user id:
public override string MakeCacheKey(System.Web.Http.Controllers.HttpActionContext context, System.Net.Http.Headers.MediaTypeHeaderValue mediaType, bool excludeQueryString = false)
{
var key = base.MakeCacheKey(context, mediaType, excludeQueryString);
return string.Format("{0}:{1}", key, userService.CurrentUser.UserID);
}
The UserID here is ultimately pulled from the user authorization cookie.
This seems to work fine.
However, I have another action that will let the user save their custom thing and obviously when I POST here I want to invalidate the cache, so it looks something like this:
[HttpPost]
[InvalidateCacheOutput("GetThings")]
public void SaveUserThing(UserThingModel thing)
{
service.Save(thing);
}
The problem (or rather the inefficiency) here is that from my understanding this will flush everything under this control and GetThings (the base key for all caches) which will include the cache for every user. This means if Bob saves a new thing, I'm going to force Sally to have to get a whole new list of things, even though her list won't have changed.
Is there an easy way around this? I suspect the problem lies in CacheOutputConfiguration.MakeBaseCacheKey, but there doesn't seem to be a mechanism to override that functionality to have it build a base key from controller, action and userId.
I could probably just grab the source from GitHub and adapt to suit my needs, but I wanted to be sure I wasn't a) missing something obvious and b) barking up the wrong tree.

ASP MVC3 keeping variable value through page refresh

Yo! As the topic says, I need to keep var's value through refresh. Thing is it's SessionKey. Other thing is it's generated automatically.
What I need to do is html <select> which won't lose data on refresh. Actually there're 2 <select>s which are filled programatically and you can pass data between them in real time. Then if I press save and page fails to validate these <select>s return to their original state. I already have it fixed, by keeping data in session and if it has certain key, <select>s are filled with correct data.
Why would I need automatically generated key? Well multi-tab working. If user would try to add 2+ new records to database at the same time (which is extreme, but possible), he needs to have that data kept under different keys so app can find desired stuff.
I could as well make client side validation, but... nope, just nope, too much work.
As for code, anything useful:
public ActionResult MethodUsedAfterPageLoad
{
...
Guid stronyGuid = Guid.NewGuid();
ViewData["strony"] = stronyGuid.ToString();
...
}
This way every refresh creates new Guid, but Guid is used as SessionKey!
If I do it following way:
public Class ControllerClass
{
private Guid stronyGuid;
...
}
This will reset variable, that's bad. Using static keyword is bad idea.

How to change a single querystring parameter, possibly via a control action?

In the last three days I've struggled trying to find a way to accomplish what I though was supposed to be a simple thing. Doing this on my own or searching for a solution in the web, didn't help. Maybe because I'm not even sure what to look for, when I do my researches.
I'll try to explain as much as I can here: maybe someone will be able to help me.
I won't say how I'm doing it, because I've tried to do it in many ways and none of them worked for different reasons: I prefer to see a fresh advice from you.
In most of the pages of web application, I have two links (but they could be more) like that:
Option A
Option B
This is partial view, retured by a controller action.
User can select or both (all) values, but they can't never select none of them: meaning that at least one must be always selected.
These links must che accessible in almost all pages and they are not supposed to redirect to a different page, but only to store this information somewhere, to be reused when action needs to filter returned contents: a place always accessible, regarding the current controller, action or user (including non authenticated users) (session? cookie?).
This information is used to filter displayed contents in the whole web application.
So, the problem is not how to create the business logi of that, but how (and where) to store this information:
without messing with the querystring (means: keeps the querystring as empty/clean as possible)
without redirecting to other pages (user must get the current page, just with different contents)
allow this information to persists between all views, until user click again to change the option(s)
My aim is to have this information stored in a model that will contains all options and their selection status (on/off), so the appropriates PartialView will know how to display them.
Also, I could send this model to the "thing" that will handle option changes.
Thanks.
UPDATE
Following Paul's advice, I've took the Session way:
private List<OptionSelectionModel> _userOptionPreferences;
protected List<OptionSelectionModel> UserOptionPreferences
{
get
{
if (Session["UserOptionPreferences"] == null)
{
_userOptionPreferences= Lib.Options.GetOptionSelectionModelList();
}
else
{
_userOptionPreferences= Session["UserOptionPreferences"].ToString().Deserialize<List<OptionSelectionModel>>();
}
if (_userOptionPreferences.Where(g => g.Selected).Count() == 0)
{
foreach (var userOptionPreferencesin _userOptionPreferences)
{
userOptionPreferences.Selected = true;
}
}
UserOptionPreferences= _userOptionPreferences;
return _userOptionPreferences;
}
private set
{
_userOptionPreferences= value;
Session["UserOptionPreferences"] = _userOptionPreferences.SerializeObject();
}
}
Following this, I've overridden (not sure is the right conjugation of "to override" :) OnActionExecuting():
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
GetOptionSelections();
base.OnActionExecuting(filterContext);
}
GetOptionSelections()...
private void GetOptionSelections()
{
if (String.IsNullOrEmpty(Request["optionCode"])) return;
var newOptionCode = Request["optionCode "];
foreach (var userOptionPreferencesin UserOptionPreferences)
{
if (userOptionPreferences.OptionCode == newOptionCode )
userOptionPreferences.Selected = !userOptionPreferences.Selected;
}
}
This code I think can be better, but right now I just want to make it work and it doesn't.
Maybe there are also other issues there (quite sure, actually), but I believe the main issue is that OnActionExecuting is called by each action in a controller that inherit from BaseController, therefore it keeps toggling userOptionPreferences.Selected on/off, but I don't know how to make GetOptionSelections() being called only once in each View: something like the old Page_Load, but for MVC.
Last update AKA solution
Ok, using the session way, I've managed to store this information.
The other issue wasn't really on topic with this question and I've managed to solve it creating a new action that take cares of handling the option's change, then redirects to the caller URL (using the usual returnUrl parameter, but as action parameter).
This way, the option change is done only once per call.
The only thing I don't really like is that I can't simply work with the UserOptionPreferences property, as it doesn't change the session value, but only the value in memory, so I have to set the property with the new object's status each time: not a big deal, but not nice either.
This is a place to use session.
The session will keep your setting between requests while keeping it out of the url querystring. It seems that you have probably tried this already, but try it again and if you have problems ask again. I think it will be the best way for you to solve this problem.

Resources