Replacing content in Razor selectively - asp.net-mvc

Is it possible to do something like this in Razor:
#using(Html.CMSContent("TermsOfService"))
{
<text>
<!-- Some default content goes here if the "TermsOfService"
item doesn't exist in my content table in the database -->
</text>
}
When the view is being processed, if the CMSContent helper doesn't find a content item named "TermsOfService" in some database table I've set up, it'll output the default content.
Otherwise, it'll replace the default content with what's in the database?
I'm trying to build a CMS/application hybrid, so that the Marketing department can override the text in my app if they want.
CLARIFY
The reason I'm doing this is to make the code super-easy to read and write. I'm going to end up with these little content blocks all over the place, so I'm hoping to minimize how much a developer has to type to get this functionality.
I'm also expecting that 99% of the time, the default text is what's going to show up. But I'd like to have the 1% cases handled where we need to quickly change some text without having to re-deploy the app. So, having the default text as close to its context as possible is important (ie, I don't want the developer to have to jump to a different file to see what the default text is - nor have to jump to the top of the page).
For example, when you do #using(Html.BeginForm()) it'll automatically emit the start and end <form> tags. I'm hoping for something clever like that.

What does Html.CMSContent("..") return? Could you store the return value of this helper into a variable and then check to see if the call returned a database value, if so display content otherwise display default text? Here is some quick code for what I describe not sure if it works 100% in your case since you did not specify what your helper returns
#{
var terms = Html.CMSContent("TermsOfService");
}
#if (terms != null && terms != "") {
#terms
} else {
#: Default Terms of Service
}

I think I figured out what I was looking for. It uses an if instead of using:
if(!Html.CMSContent("TermsOfService"))
{
<text>
Default content goes here
</text>
}
For the helper:
public static bool CMSContent(this HtmlHelper Html, string BlockName)
{
if (blockname in database)
{
var blockdata = (retrieve from database);
Html.ViewContext.HttpContext.Response.Write(blockdata);
return true;
}
else
{
return false;
}
}
Although I'm not sure that using Response.Write is the best way to do it.

Related

When using ASP.NET MVC, what is the best way to update multiple page sections with one HTML.Action() method?

I have a pretty big ASP.NET MVC site with 100 controllers and thousands of actions. Previously the header image that was defined on the Site.Master page was hardcoded and I want to make it dynamic.
To do so, I added this line to my Site.Master file:
<%= Html.Action("GetHeaderTitle", "Home")%>
which just returns some HTML for the header title such as:
<span style='font-size:15px;'>My Header Title</span>
The issue is that <title> also had this same hard coded value. I could obviously create another HTML.Action to have it show the dynamic valid in the title, but now I am going back to the server twice for essentially the same information (not the exact same HTML as I don't want the span information, but the same logic on the server to get the data).
Is there a way to have an Html.Action return multiple snippets of HTML that I can updates in different places on my master page?
I think you're looking at it wrong - if retrieving of the title is a long operation then just cache the results and write different actions anyway.
// Controller
public string GetTitle()
{
var title = (string)ControllerContext.HttpContext.Items["CachedTitle"];
if (string.IsNullOrEmpty(title))
{
title = "some lengthy retrieval";
ControllerContext.HttpContext.Items["CachedTitle"] = title;
}
return title;
}
public ActionResult GetTitleForTitle()
{
return Content(GetTitle());
}
public ActionResult GetHeaderTitle()
{
return Content("<span>"+ GetTitle() + "<span>");
}
Alternatively, you can cache it directly on the view, which is kind of evil (the simpler view the better):
<%
ViewBag.CachedTitle = Html.Action("GetHeaderTitle", "Home");
%>
...
<%= ViewBag.CachedTitle %>
...
<%= ViewBag.CachedTitle %>
Are you using <asp:ContentPlaceHolder ID="TitleContent" runat="server" /> ? in your site.master? Or have you considered using it? This would allow you to set the title from within a view based on your model.
Maybe you should also consider to introduce ViewModels, which allows you to combine view related data into a ViewModel and return it from a controller. This would allow you to batch queries and save round trips. And use a data repository which gets injected into your controller class (if you are not already doing). Sorry I'm guessing here because you do not show any controller code.
Or you should take advantage of client side code (JavaScript) and load parts of the UI via ajax? And have the UI update itself (could also be reactive).
Unfortunately I have the feeling that the thing your are facing today is not about the title but more about the hundreds of controllers with thousands of actions.
So researching how to organize controllers and building up an opinion based on your use cases would be my best bet based on what I can see and guess from your question.
Hope that helps...

Using File.Exists() to check if views exist on disk

I'd like to build a way to override for ASP.NET MVC views at runtime. Basic idea is to have MyView.cshtml and and optional MyView.Override.cshtml. As soon as MyView.Override.cshtml is present on the disk, it shall be used instead of the original view.
What I'm trying to do is:
protected new ActionResult View(string viewName)
{
var overridePath = viewName.Replace(".cshtml", ".Override.cshtml"); // i.e. ~/Views/MyView.Override.cshtml
if (System.IO.File.Exists(Server.MapPath(overridePath)))
{
return base.View(overridePath); // use override
}
return base.View(viewName); // use default
}
This basically works. My question: Is it good practice to do use File.Exists() when resolving views? Will this introduce any issues (Flexibility, Performance)? Am I missing a standard MVC way to do the same thing?
If you want this possibility for EVERY page in your application, then there is already a way to do this called DisplayModeProvider. The typical use-case for this is for having mobile or browser-specific Views.
For example, you can set up an Index.Mobile.cshtml, and the built-in provider will return that view instead of Index.cshtml if it determines that the requesting browser is a mobile device based on the User Agent string.
However, you can create your own provider to add additional ones that will return for whatever reason you wish.
Inside your Startup or Global.asax (depending on OWIN, etc), you will want to add the following code:
DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("Override")
{
// Make it always try this
ContextCondition = (ctx => true)
});
With this in your code, it will always try to return a xxxx.Override.cshtml view. If that doesn't exist, it will fall back to the regular view.

Return HtmlString From Model & Display Within View

Let me preface this by saying that I know that I can use HTML.Raw() to display HTML contained in a string. However, I believe that the purpose of MVC is to separate the code logic and to allow front end developers to focus on UI and not logic. Therefore, I try to pass everything I can from the model exactly as I want it to look.
That brings me to my question. I have a model that contains an address. I have written a function that returns the address in a few different versions (single line, two line, multiline) and I'm setting these as HtmlString objects.
public HtmlString TwoLine { get { return ReturnFullAddress(2); } }
/* function removed for brevity */
However, when I write the following razor code:
#Html.DisplayFor(modelItem => item.Address.TwoLine)
No text is returned within the view. When I debug this, there IS a value properly assigned within Address.TwoLine, but it is within { } (which I thought was strange).
How do I make this work, or why doesn't this work?
DisplayFor() doesn't know how to handle HtmlString properties.
You should just print the value directly: #Model.Address.TwoLine

Keep my view as clean as possible (for clarity)

In my view (asp.net mvc razor) I would like to display a description (from my model) in french or dutch based on current thread culture. Below is my actual implementation.
<td>#item.Title</td>
<td>#item.SubTitle</td>
#if (Thread.CurrentThread.CurrentCulture.Name == "fr-BE") {
<td>#item.MaterialPacking.DescriptionFr</td>
} else {
<td>#item.MaterialPacking.DescriptionNl</td>
}
<td>#item.Quantity</td>
...
I think the code clarity is not optimal but I don't think creating a helper specific for this is necessary. Are there any other possibilities?
Thanks.
UPDATE
Here is an extract of data I retrieve from my repository.
As you can see I have 2 possibilities: ...fr or ...nl
I need a specific item based on the current culture.
here is the linq:
var request = requestRepository.Find(x => x.RequestID == requestID)
.MyInclude(x => x.TransportedMaterials.Select(y => y.MaterialPacking)).FirstOrDefault();
return request.TransportedMaterials;
If you use a custom ViewModel, you can make it the controller's responsibility to populate the MaterialPacking property with the message in the correct language. That moves the code out of the view.
However, the if/else statement is still bad practice. What happens if you decide to support Spanish? Do you want to modify every one of these if statements throughout the code? You should create a service where you can pass it the key for a message and it will give you back the actual message in the current language.
So your controller code would end up saying something like this:
item.MaterialPackingDescription =
_languageService.GetDescription(item.MaterialPacking);
And your view code:
<td>#item.MaterialPackingDescription</td>
You don't need special casing (it wouldn't scale: if you were to add one more language, you'd have to go in an add a new else block everywhere you do this). The way to get localized strings is to use the built-in resource manager. Visual Studio makes this very easy... Look it up (or look up localization) in MSDN.
So your code would become:
<td>#item.Title</td>
<td>#item.SubTitle</td>
<td>#Resources.MaterialPackingDescription</td>
<td>#item.Quantity</td>
...
Note that you are also using the wrong property: For resources you should use Thread.CurrentThread.CurrentUICulture rather than CurrentCulture.
Why not perform this logic in your controller or view model. Then you wouldn't need to do it in your view.
Take a look at this blogpost about Localization.
With resource files you'll be able to keep your view cleaner because there's no need for if constructions to display messages in the correct language.
You must store your string values in App_GlobalResources, and then use it from there.
Check this article for more help

.NET MVC: How to implement different page appearance per user?

I am running out of ideas here. Maybe you can advice me what pattern or method(s) to use.
User should be able to log in and change the appearance only for his/her profile.
The difference (AFAIK) with personalization is that personalized layout are seen only for the editor (him-/herself).
The difference between skinning, I guess, is that Skins are predefined but users should be able to change the settings themselves.
I need to be able to display the customized layout to everyone who visit author`s page.
The good solution would be to keep the layout info in a DB table. Also it should be cached I guess to take load off the DB and used in CSS.
Thanks
Edit:
OK I have done some research now. Came up with this kind of idea.
In a View get a userId (Guid type) from a DB and set it to the ViewData:
ViewData["userId"] = profile.userId;
That View uses the following MasterPage called 'Profile.Master' and links to the dynamic CSS file:
<link href="<%= Url.Action("Style", "Profile",
ViewData["userId"]) %>" rel="stylesheet" type="text/css" />
</head>
In the ProfileController get the CSS data from DB and return it to the dynamic CSS View:
public ActionResult Style(Guid userId)
{
var styles = (from s in Db.UserStyleSet.OfType<UserStyle>()
where s.aspnet_Users.UserId == userId
select s);
return View("Style", styles);
}
The problem is that the UserId is never passed to the dynamic CSS link:
The parameters dictionary contains a null entry for parameter 'userId' of non-nullable type 'System.Guid' for method 'System.Web.Mvc.ActionResult Style(System.Guid)' in 'Project.Controllers.ProfileController'.
Any advice is welcome, thank you.
Very neat layout customization features you can find in Kona project developed by Rob Conery. When you run source code which you can find here, you will see layout management UI which allows you to change the position of each component on the screen.
The approach used there is as follows:
When page is rendered our customized view engine check which master page should present (this way we are able to switch themes based on current settings)
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
ViewEngineResult result = null;
var request = controllerContext.RequestContext;
if (controllerContext.Controller.GetType().BaseType == typeof(KonaController)) {
var orchardController = controllerContext.Controller as KonaController;
string template = orchardController.ThemeName;
View engine uses master page and renders view which was defined by specific controller action resolved using route tables. For instance, we typed main url of the site which pointed to Home Controller, Index method. This method returned Index.aspx view which was rendered by View engine.
While view engine is rendering the Index.aspx page it launches helper methods like
<%this.RenderWidgets("sidebar1"); %>.
This method is truely responsible for rendering specific widdgets per each div in the aspx page. This way, if your user changes the layout of the widgets they will be correctly presented on the screen.
public static void RenderWidgets(this ViewPage pg, Kona.Infrastructure.Page page, bool useEditor, string zone) {
if (page != null) {
foreach (IWidget widget in page.Widgets.Where(x => x.Zone.Equals(zone, StringComparison.InvariantCultureIgnoreCase))) {
string viewName = useEditor ? widget.EditorName : widget.ViewName;
if (widget.ViewName != null) {
if (widget.IsTyped) {
var typedWidget = widget as Widget<IList<Product>>;
pg.Html.RenderPartial(viewName, typedWidget);
} else {
pg.Html.RenderPartial(viewName, widget);
}
} else if (!string.IsNullOrEmpty(widget.Title)) {
pg.Html.RenderPartial("TitleAndText", widget);
} else {
pg.Html.RenderPartial("TextOnly", widget);
}
}
}
}
How user is able to change the layout? Kona has very neat javascript which is used together with Ajax and user simply drag&drop widgets from one panel to another to reorder the layout.
You could use a CMS framework. See this question for suggestions
You could dynamically build a CSS file and save the css name in the user's db entry.
How much customisation do you need? Storing an entire css in the database 1 style at a time seems a little overkill, are you sure your users really need / want that level of customisation?
Wouldn't it be simpler to present a list of themes, allow the user to select the one they want and then store that information with the user profile so that when you retrieve the profile details you also retrieve the theme. This information can then be used to select the appropriate master as well as passed to the view to render the correct stylesheet(s).
If you really want to allow extreme customisation down to the individual style level, I would use a default css and then when the user customises their layout, copy the default and alter as necessary, creating a custom css for the user. Each time the user updates their profile layout, simply update the css file with the changes. To get around css caching, record an incrementing version number for each change and append that to the end of the url for the css e.g. <link rel="stylesheet" href="user001.css?v=2>.

Resources