Your question is pretty broad. All my comments/answer are going to be in Razor. In my opinion, if you're going to switch to MVC, you might as well use Razor. There are plenty of good reasons to switch, but I'd say my top pick for anyone who is migrating, the best reason is two fold; first razor allowed me to drop some bad habits about how programming works with webforms and in the same manor it forced me to re-write my code instead of trying to copy and paste and change the original code to work in MVC.
The control adds some JS/CSS to the page, has some server-side logic and custom properties.
What should it be in an MVC app?
This is a pretty religious software question. There are plenty of ways of doing it.
So onto my religous opinion about how to add JS/CSS in MVC. One way to include server side is to create a region in the Layout/Master template.
Views/_ViewStart.cshtml
@{
Layout = "~/Views/Shared/Layout.cshtml";
}
Views/Shared/Layout.cshtml
<!doctype html>
<head>
<link href="/Styles/Global.css" rel="stylesheet" type="text/css" />
@RenderSection("Styles", required: false)
<script src="/Scripts/Global.js" type="text/javascript"></script>
@RenderSection("Scritps", required: false)
This would allow each view to optionally (because required=false
) add any Styles or Scripts using the RenderSecion code.
Views/Home/Index.cshtml
@section Styles {
<link href="/Styles/Home/Index.css" rel="stylesheet" type="text/css" />
}
This is pretty simple, and probably a good solution for many simple to moderately complex sites. This wasn't enough for me, as I needed to do what you requested, and only include files if they were truly needed. Additionally, partial views and templates cannot render sections, which I found to be a giant PITA. So I added some HtmlHelper extension methods. (i'm only going to show the code for Javascript as the Stylesheets are nearly Identical code. These methods don't allow duplicate urls.
Domain/HtmlHelperExtensions.cshtml
public static class HtmlHelperExtensions
{
private const string _JSViewDataName = "RenderJavaScript";
private const string _StyleViewDataName = "RenderStyle";
public static void AddJavaScript(this HtmlHelper HtmlHelper, string ScriptURL)
{
List<string> scriptList = HtmlHelper.ViewContext.HttpContext.Items[HtmlHelperExtensions._JSViewDataName] as List<string>;
if (scriptList != null)
{
if (!scriptList.Contains(ScriptURL))
{
scriptList.Add(ScriptURL);
}
}
else
{
scriptList = new List<string>();
scriptList.Add(ScriptURL);
HtmlHelper.ViewContext.HttpContext.Items.Add(HtmlHelperExtensions._JSViewDataName, scriptList);
}
}
public static MvcHtmlString RenderJavaScripts(this HtmlHelper HtmlHelper)
{
StringBuilder result = new StringBuilder();
List<string> scriptList = HtmlHelper.ViewContext.HttpContext.Items[HtmlHelperExtensions._JSViewDataName] as List<string>;
if (scriptList != null)
{
foreach (string script in scriptList)
{
result.AppendLine(string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", script));
}
}
return MvcHtmlString.Create(result.ToString());
}
}
Updated Views/Shared/Layout.cshtml
<!doctype html>
<head>
<link href="/Styles/Global.css" rel="stylesheet" type="text/css" />
@Html.RenderStyleSheets()
<script src="/Scripts/Global.js" type="text/javascript"></script>
@Html.RenderJavascripts()
Updated Views/Home/Index.cshtml
@Html.AddStyleSheet("/Styles/Home/Index.css")
Onto your next question...
How do I encapsulate those things into one reusable entity in an MVC app? Maybe a partial view?
Razor supports both partial views and templates. Although technically there is large overlap in what each can do, there are limitations of how each were designed to allow a programmer to take advantage of each when needed.
Partial views do not require a Model/Class in order to be rendered. Here is a completely valid partial view:
/Views/Home/Index.cshtml
@Html.Partial("Partial-Menu")
/Views/Shared/Partial-Menu.cshtml
<div id="menu">
<a href="/">Home</a>
<a href="/Home/About">About Us</a>
</div>
Templates on the other had, do required a Model in order to be rendered. This is because Templates are a way to render any class or struct as either an Editing Template or a Display Template.
/Models/Person.cs
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
/Controllers/HomeController.cs
public ActionResult Index()
{
Person model = new Person();
model.FirstName = "Jon";
model.LastName = "Doe";
return this.View(model);
}
/Views/Home/Index.cshtml
@model Project1.Models.Person
@Html.DisplayModel()
@Html.EditforModel()
/Views/Shared/DisplayTemplates/Person.cshtml
@model Project1.Models.Person
<div>@Html.LabelFor(x => x.FirstName) @Html.DisplayFor(x => x.FirstName)</div>
<div>@Html.LabelFor(x => x.LastName) @Html.DisplayFor(x => x.LastName)</div>
/Views/Shared/EditorTemplates/Person.cshtml
@model Project1.Models.Person
<div>@Html.LabelFor(x => x.FirstName) @Html.EditorFor(x => x.FirstName)</div>
<div>@Html.LabelFor(x => x.LastName) @Html.EditrorFor(x => x.LastName)</div>
The way I see it, any model that might turn into a Form for data entry should probably be a Template. This is the way I prefer to encapsulate my models. Using the extension methods provided earlier, both my partial views and templates can load includes as needed (currently only one of my models of oodles of them actually needed to use this).
My preference is to have up to three includes (of each Javascript and Styles) per page rendered. I basically have a Global.css/js, a controller Home.css/js, and a page Index.css/js as the possible includes. It's been very seldom that I have a controller include.