Localization in ASP.NET MVC 4 using App_GlobalResources - asp.net

I am trying to accomplish two things:
Localize the “built-in” error messages for “FieldMustBeDate” and "FieldMustBeNumeric".
Localize some of the other error messages you would encounter, for example, "PropertyValueRequired".
By using http://forums.asp.net/t/1862672.aspx/1 for problem 1 and MVC 4 ignores DefaultModelBinder.ResourceClassKey for problem 2 I have managed to get both working locally.
However as soon as I publish to a website, the “built-in” error messages defaults back to English while the other error messages stay localized.
I have read several places that using the App_GlobalResources should be avoided, however I am unable to accomplish problem 1 without using this.
I have created a .resx file with the name “WebResources.resx”, set the Build Action to “Embedded”, set the Copy to Output Directory to “Do no Copy”, set the Custom Tool to “PublicResXFileCodeGenerator” and set the Custom Tool Namespace to “Resources”.
The Project itself is set to only Publish files that are needed.
My Global.asax.cs contains the following (relevant) code:
ClientDataTypeModelValidatorProvider.ResourceClassKey = "WebResources";
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(RequiredAttribute),
typeof(MyRequiredAttributeAdapter));
And the class MyRequiredAttributeAdapter contains the following code:
public class MyRequiredAttributeAdapter : RequiredAttributeAdapter
{
public MyRequiredAttributeAdapter(
ModelMetadata metadata,
ControllerContext context,
RequiredAttribute attribute
)
: base(metadata, context, attribute)
{
if (attribute.ErrorMessageResourceType == null)
{
attribute.ErrorMessageResourceType = typeof(Resources.WebResources);
}
if (attribute.ErrorMessageResourceName == null)
{
attribute.ErrorMessageResourceName = "PropertyValueRequired";
}
}
}
This is working locally however does anyone have any idea on how to get the "built in" messages to work after this is published?
Thank you for your help!
Best regards,
Andreas

I figured this one out myself. If you are trying to accomplish the above you must separate the localized error messages.
Create a *.resx file for the other error messages fx "PropertyValueRequired" and set the Build Action to “Embedded”, set the Copy to Output Directory to “Do no Copy”, set the Custom Tool to “PublicResXFileCodeGenerator” and set the Custom Tool Namespace to “Resources”.
In my case I have moved "PropertyValueRequired" to a file called LocalDanish.resx (still in the App_GlobalResources folder) and changed the line in my "MyRequiredAttributeAdapter" from
attribute.ErrorMessageResourceType = typeof(Resources.WebResources);
to
attribute.ErrorMessageResourceType = typeof(Resources.LocalDanish);
In order to get the "built in" error messages to work, you must create two *.resx files. I have created WebResources.resx and WebResources.da.resx. Do NOT change anything, leave the settings on them on default (Build Action to "Content", etc.). I guess the website automatically looks for the *.da.resx files in my case because I have set the globalization in my WebConfig:
<globalization uiCulture="da-DK" culture="da-DK"/>
Hope this helps anybody.
Best regards,
Andreas

I have made some minor additions to the original post, which didn't translate all messages in my case.
(String length and invalid property values)
Follow the above steps, to create the *.resx files, set their properties, and then set the locale in web.config, as described by Andreas.
Then create a couple of adapters:
// As described in original post:
public class LocalizedRequiredAttributeAdapter : RequiredAttributeAdapter
{
public LocalizedRequiredAttributeAdapter(
ModelMetadata metadata,
ControllerContext context,
RequiredAttribute attribute
)
: base(metadata, context, attribute)
{
if (attribute.ErrorMessageResourceType == null)
attribute.ErrorMessageResourceType = typeof(Resources.Resources);
if (attribute.ErrorMessageResourceName == null)
attribute.ErrorMessageResourceName = "PropertyValueRequired";
}
}
// Addition to original post:
public class LocalizedStringLengthAttributeAdapter : StringLengthAttributeAdapter
{
public LocalizedStringLengthAttributeAdapter(
ModelMetadata metadata,
ControllerContext context,
StringLengthAttribute attribute
)
: base(metadata, context, attribute)
{
if (attribute.ErrorMessageResourceType == null)
attribute.ErrorMessageResourceType = typeof(Resources.Resources);
if (attribute.ErrorMessageResourceName == null)
attribute.ErrorMessageResourceName = "StringLengthAttribute_ValidationError";
}
}
And in Global.asax.cx:
// Addition to original post: (Used for "PropertyValueInvalid")
DefaultModelBinder.ResourceClassKey = "Resources";
// As described in original post:
ClientDataTypeModelValidatorProvider.ResourceClassKey = "Resources";
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredAttribute), typeof(LocalizedRequiredAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(StringLengthAttribute), typeof(LocalizedStringLengthAttributeAdapter));

Related

.Net Core 3.1 Razor Pages : autoredirect to culture

I try to accomplish a similar behaviour with MS Docs.
For example, if you visit https://learn.microsoft.com/, you will be redirected to your culture, in my case I'm being redirected automatically to https://learn.microsoft.com/en-gb/.
Same goes for inner pages if you access them without the culture in the URL.
For instance, by accessing:
https://learn.microsoft.com/aspnet/core/razor-pages/?view=aspnetcore-3.1&tabs=visual-studio
it will be automatically redirect you to:
https://learn.microsoft.com/en-gb/aspnet/core/razor-pages/?view=aspnetcore-3.1&tabs=visual-studio
I have a small demo app where I conduct my localisation experiment for .NET Core 3.1 and Razor Pages here.
I have set options.Conventions here, and I have created CustomCultureRouteRouteModelConvention class here, but I'm fairly novice with .NET Core and I'm kind of stuck on how to implement the above-described functionality.
Thank you all in advance!
You should use existing Rewriting Middleware to do redirects: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?view=aspnetcore-3.1
In the simplest form, you can tell rewrite middleware to redirect if it does not see a locale pattern at the beginning of the URL path, maybe
new RewriteOptions() .AddRedirect("^([a-z]{2}-[a-z]{2})", "en-US/$1")
(regex not tested) or do full redirect class with more detailed rules when and to what locale you want to redirect. Example in that aspnet document references RedirectImageRequest which you can use to get an understanding of how custom redirect rules works. Adapting to your case as a proof of concept, I reused most of the logic in your existing RedirectUnsupportedCulture:
public class RedirectUnsupportedCultures : IRule
{
private readonly string _extension;
private readonly PathString _newPath;
private IList<CultureInfo> _cultureItems;
private string _cultureRouteKey;
public RedirectUnsupportedCultures(IOptions<RequestLocalizationOptions> options)
{
RouteDataRequestCultureProvider provider = options.Value.RequestCultureProviders
.OfType<RouteDataRequestCultureProvider>()
.First();
_cultureItems = options.Value.SupportedUICultures;
_cultureRouteKey = provider.RouteDataStringKey;
}
public void ApplyRule(RewriteContext rewriteContext)
{
// do not redirect static assets and do not redirect from a controller that is meant to set the locale
// similar to how you would not restrict a guest user from login form on public site.
if (rewriteContext.HttpContext.Request.Path.Value.EndsWith(".ico") ||
rewriteContext.HttpContext.Request.Path.Value.Contains("change-culture"))
{
return;
}
IRequestCultureFeature cultureFeature = rewriteContext.HttpContext.Features.Get<IRequestCultureFeature>();
string actualCulture = cultureFeature?.RequestCulture.Culture.Name;
string requestedCulture = rewriteContext.HttpContext.GetRouteValue(_cultureRouteKey)?.ToString();
// Here you can add more rules to redirect based on maybe cookie setting, or even language options saved in database user profile
if(string.IsNullOrEmpty(requestedCulture) || _cultureItems.All(x => x.Name != requestedCulture)
&& !string.Equals(requestedCulture, actualCulture, StringComparison.OrdinalIgnoreCase))
{
string localizedPath = $"/{actualCulture}{rewriteContext.HttpContext.Request.Path.Value}";
HttpResponse response = rewriteContext.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
rewriteContext.Result = RuleResult.EndResponse;
// preserve query part parameters of the URL (?parameters) if there were any
response.Headers[HeaderNames.Location] =
localizedPath + rewriteContext.HttpContext.Request.QueryString;
}
}
and registered it in Startup.cs with
// Attempt to make auto-redirect to culture if it is not exist in the url
RewriteOptions rewriter = new RewriteOptions();
rewriter.Add(new RedirectUnsupportedCultures(app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>()));
app.UseRewriter(rewriter);
Improvement:
After using the above code I bumped on a bug that in case the culture is not supported by the application, the redirection will end up with infinite culture paths. For example, if I support the cultures en (default) and gr, if instead of either /en/foobar or /gr/foobar I would write /fr/foobar, I would end up getting /en/fr/foobar then /en/en/fr/foobar and etc.
I added private readonly LinkGenerator _linkGenerator; to the class, which I initialise it in the constructor. I removed that line string localizedPath = $"/{actualCulture}{rewriteContext.HttpContext.Request.Path.Value}"; and the code after that line looks like this:
rewriteContext.HttpContext.GetRouteData().Values[_cultureRouteKey] = actualCulture;
HttpResponse response = rewriteContext.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
rewriteContext.Result = RuleResult.EndResponse;
// preserve query part parameters of the URL (?parameters) if there were any
response.Headers[HeaderNames.Location] =
_linkGenerator.GetPathByAction(
rewriteContext.HttpContext,
values: rewriteContext.HttpContext.GetRouteData().Values
)
+ rewriteContext.HttpContext.Request.QueryString;
As decribed in Microsoft docs localization middleware; each the localization request initializes a list of RequestCultureProvider and is enumerated by the below order :
QueryStringRequestCultureProvider : e.g. http://localhost:1234/Index?culture=en
CookieRequestCultureProvider : Looks for the culture cookie, and it will be null if you haven't set it manually.
AcceptLanguageHeaderRequestCultureProvider : This one depends on the browsers cultures adn this is what you need to look for.
To make sure how it works, delete the culture cookie and change the browser language preferences by moving the desired language to the top, you will see that the language is selected according to the browser preferences.

Alfresco Object is not available in extension module in alfresco share

I am trying to override the javascript controller node-header.js of components\node-details with the extension module of alfresco share
This is my node-header.get.js
<import resource="classpath:/alfresco/templates/org/alfresco/import/alfresco-util.js">
for (var i=0; i<model.widgets.length; i++)
{
if (model.widgets[i].id == "NodeHeader")
{
if(model.widgets[i].options.nodeRef!=null)
{
var jsNode = new Alfresco.util.Node(model.widgets[i].options.nodeRef);
if(jsNode.hasAspect("custom:intranetFile")){
model.widgets[i].options.showFavourite = false;
model.widgets[i].options.showLikes = false;
}
}
}
}
I am getting this error
Error Message: 05270002 Failed to execute script
'classpath*:webscripts/custom/nodeheader/hidelikesync/node-header.get.js':
05270001 ReferenceError: "Alfresco" is not defined.
(jar:file:/C:/Alfresco/Alfresco42/tomcat/webapps/share/WEB-INF/lib/customshare.jar!/webscripts/custom/nodeheader/hidelikesync/node-header.get.js#1555)
Error lies in this line
var jsNode = new Alfresco.util.Node(model.widgets[i].options.nodeRef);
as Alfresco object is not available how can I get it?
Based on my answer yesterday on the share-extras-devel list:
Your issue is that you are mixing up your web script JS with client-side JavaScript. Alfresco.util.Node is a client-side helper class and is therefore available to client-side JS running in the web browser, but not to your web script code which runs on the server.
If you look at the source of alfresco-util.js, which you are including, you will see that there is a helper class there but it is called AlfrescoUtil.
To get some information on this given node I would suggest that you want to use the static method AlfrescoUtil.getNodeDetails() from that class, e.g.
var jsNode = AlfrescoUtil.getNodeDetails(model.widgets[i].options.nodeRef);
The structure of the jsNode object will be as per the JSON returned by the doclist-v2 webscripts, so you should be able to check for the presence of your custom aspect in the aspects array property.
If you check the source of alfresco-util.js you will see that additional parameters are also supported by getNodeDetails(). It seems to me you can also pass in an optional site name, plus some options if you wish.

Javascript permission denied error when using Atalasoft DotImage

Have a real puzzler here. I'm using Atalasoft DotImage to allow the user to add some annotations to an image. When I add two annotations of the same type that contain text that have the same name, I get a javascript permission denied error in the Atalasoft's compressed js. The error is accessing the style member of a rule:
In the debugger (Visual Studio 2010 .Net 4.0) I can access
h._rule
but not
h._rule.style
What in javascript would cause permission denied when accessing a membere of an object?
Just wondering if anyone else has encountered this. I see several people using Atalasoft on SO and I even saw a response from someone with Atalasoft. And yes, I'm talking to them, but it never hurts to throw it out to the crowd. This only happens in IE8, not FireFox.
Thanks, Brian
Updates: Yes, using latest version: 9.0.2.43666
By same name (see comment below) I mean, I created default annotations and they are named so they can be added with javascript later.
// create a default annotation
TextData text = new TextData();
text.Name = "DefaultTextAnnotation";
text.Text = "Default Text Annotation:\n double-click to edit";
//text.Font = new AnnotationFont("Arial", 12f);
text.Font = new AnnotationFont(_strAnnotationFontName, _fltAnnotationFontSize);
text.Font.Bold = true;
text.FontBrush = new AnnotationBrush(Color.Black);
text.Fill = new AnnotationBrush(Color.Ivory);
text.Outline = new AnnotationPen(new AnnotationBrush(Color.White), 2);
WebAnnotationViewer1.Annotations.DefaultAnnotations.Add(text);
In javascript:
CreateAnnotation('TextData', 'DefaultTextAnnotation');
function CreateAnnotation(type, name) {
SetAnnotationModified(true);
WebAnnotationViewer1.DeselectAll();
var ann = WebAnnotationViewer1.CreateAnnotation(type, name);
WebThumbnailViewer1.Update();
}
There was a bug in an earlier version that allowed annotations to be saved with the same unique id's. This generally doesn't cause problems for any annotations except for TextAnnotations, since they use the unique id to create a CSS class for the text editor. CSS doesn't like having two or more classes defined by the same name, this is what causes the "Permission denied" error.
You can remove the unique id's from the annotations without it causing problems. I have provided a few code snippets below that demonstrate how this can be done. Calling ResetUniques() after you load the annotation data (on the server side) should make everything run smoothly.
-Dave C. from Atalasoft
protected void ResetUniques()
{
foreach (LayerAnnotation layerAnn in WebAnnotationViewer1.Annotations.Layers)
{
ResetLayer(layerAnn.Data as LayerData);
}
}
protected void ResetLayer(LayerData layer)
{
ResetUniqueID(layer);
foreach (AnnotationData data in layer.Items)
{
LayerData group = data as LayerData;
if (group != null)
{
ResetLayer(data as LayerData);
}
else
{
ResetUniqueID(data);
}
}
}
protected void ResetUniqueID(AnnotationData data)
{
data.SetExtraProperty("_atalaUniqueIndex", null);
}

Asp.Net single control render for AJAX calls

I'm trying to implement something similar to this or this.
I've created a user control, a web service and a web method to return the rendered html of the control, executing the ajax calls via jQuery.
All works fine, but if I put something in the user control that uses a relative path (in my case an HyperLink with NavigateUrl="~/mypage.aspx") the resolution of relative path fails in my developing server.
I'm expecting:
http://localhost:999/MyApp/mypage.aspx
But I get:
http://localhost:999/mypage.aspx
Missing 'MyApp'...
I think the problem is on the creation of the Page used to load the control:
Page page = new Page();
Control control = page.LoadControl(userControlVirtualPath);
page.Controls.Add(control);
...
But I can't figure out why....
EDIT
Just for clarity
My user control is located at ~/ascx/mycontrol.ascx
and contains a really simple structure: by now just an hyperlink with NavigateUrl like "~/mypage.aspx".
And "mypage.aspx" really resides on the root.
Then I've made up a web service to return to ajax the partial rendered control:
[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class wsAsynch : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
/* ...do some stuff with params... */
Page pageHolder = new Page();
UserControl viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
Type viewControlType = viewControl.GetType();
/* ...set control properties with reflection... */
pageHolder.Controls.Add(viewControl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
}
The html is correctly rendered, but the relative path in the NavigateUrl of hyperlink is incorrectly resolved, because when I execute the project from developing server of VS2008, the root of my application is
http://localhost:999/MyApp/
and it's fine, but the NavigateUrl is resolved as
http://localhost:999/mypage.aspx
losing /MyApp/ .
Of Course if I put my ascx in a real page, instead of the pageHolder instance used in the ws, all works fine.
Another strange thing is that if I set the hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx") I get the correct url of the page:
http://localhost:999/MyApp/mypage.aspx
And by now I'll do that, but I would understand WHY it doesn't work in the normal way.
Any idea?
The problem is that the Page-class is not intented for instantiating just like that. If we fire up Reflector we'll quickly see that the Asp.Net internals sets an important property after instantiating a Page class an returning it as a IHttpHandler. You would have to set AppRelativeTemplateSourceDirectory. This is a property that exists on the Control class and internally it sets the TemplateControlVirtualDirectory property which is used by for instance HyperLink to resolve the correct url for "~" in a link.
Its important that you set this value before calling the LoadControl method, since the value of AppRelativeTemplateSourceDirectory is passed on to the controls created by your "master" control.
How to obtain the correct value to set on your property? Use the static AppDomainAppVirtualPath on the HttpRuntime class. Soo, to sum it up... this should work;
[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
/* ...do some stuff with params... */
var pageHolder = new Page() { AppRelativeTemplateSourceDirectory = HttpRuntime.AppDomainAppVirtualPath };
var viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
var viewControlType = viewControl.GetType();
/* ...set control properties with reflection... */
pageHolder.Controls.Add(viewControl);
var output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
The tildy pust the path in the root of the app, so its going to produce a the results you are seeing. You will want to use:
NavigateUrl="./whatever.aspx"
EDIT:
Here is a link that may also prove helpful...http://msdn.microsoft.com/en-us/library/ms178116.aspx
I find the /MyApp/ root causes all sorts of issues. It doesn't really answer your question 'why is doesn't work the normal way', but do you realize you can get rid of the /MyApp/ and host your website at http:/localhost/...?
Just set Virtual Path in the website properties to '/'.
This clears everything up, unless of course you are trying to host multiple apps on the development PC at the same time.
It might be that the new page object does not have "MyApp" as root, so it is resolved to the server root as default.
My question is rather why it works with Page.ResolveUrl(...).
Maybe ResolveUrl does some more investigation about the location of the usercontrol, and resolves based on that.
Weird, I recreated the example. The hyperlink renders as <a id="ctl00_hlRawr" href="Default.aspx"></a> for a given navigation url of ~/Default.aspx. My guess is that it has something to do with the RequestMethod. On a regular page it is "GET" but on a webservice call it is a "POST".
I was unable to recreate your results with hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx")
The control always rendered as <a id="ctl00_hlRawr" href="Default.aspx"></a> given a virtual path. (Page.ResolveUrl gives me "~/Default.aspx")
I would suggest doing something like this to avoid the trouble in the future.
protected void Page_Load(object sender, EventArgs e)
{
hlRawr.NavigateUrl = FullyQualifiedApplicationPath + "/Default.aspx";
}
public static string FullyQualifiedApplicationPath
{
get
{
//Return variable declaration
string appPath = null;
//Getting the current context of HTTP request
HttpContext context = HttpContext.Current;
//Checking the current context content
if (context != null)
{
//Formatting the fully qualified website url/name
appPath = string.Format("{0}://{1}{2}{3}",
context.Request.Url.Scheme,
context.Request.Url.Host,
(context.Request.Url.Port == 80 ? string.Empty : ":" + context.Request.Url.Port),
context.Request.ApplicationPath);
}
return appPath;
}
}
Regards,
It is hard to tell what you are trying to achieve without posting the line that actually sets the Url on of the HyperLink, but I think I understand your directory structure.
However, I have never run into a situation that couldn't be solved one way or another with the ResolveUrl() method. String parsing for a temporary path that won't be used in production is not recommended because it will add more complexity to your project.
This code will resolve in any object that inherits from page (including a usercontrol):
Page page = (Page)Context.Handler;
string Url = page.ResolveUrl("~/Anything.aspx");
Another thing you could try is something like this:
Me.Parent.ResolveUrl("~/Anything.aspx");
If these aren't working, you may want to check your IIS settings to make sure your site is configured as an application.

Is there a way to get ALL the MIME types instead of wrinting a huge case statement?

I want to populate
Response.ContentType = "text/plain";
From somewhere in the server/web/dictionary ALL possible MIME types according to file extension:
public string GetMimeType(string extension)
{
//This is what I am looking for.
}
Also, I have to rename the file (at least if going to be downloaded, so I have to know in advance if it's going to be opened or not.
You can store the mimetype when the file is uploaded ( FileUpload.PostedFile.ContentType ) and send that when the file is requested.
Umm... why? You're not going to be returning content of every possible type, are you?
Here's a list of common types: http://www.webmaster-toolkit.com/mime-types.shtml. There is no list that would include "ALL" types simply because any application vendor can create a custom one and associate it with a custom extension.
It's going to depend on your platform. Here's one for C# and IIS: http://blog.crowe.co.nz/archive/2006/06/02/647.aspx
In Powershell it's a one-liner:
([adsi]"IIS://localhost/MimeMap").MimeMap
The code in the link posted by Richard:
// Maintain a sorted list to contain the MIME Types
SortedList sl = new SortedList();
Console.WriteLine("IIS Mime Map - c#");
Console.WriteLine();
// Serve to connect to...
string ServerName = "LocalHost";
// Define the path to the metabase
string MetabasePath = "IIS://" + ServerName + "/MimeMap";
// Note: This could also be something like
// string MetabasePath = "IIS://" + ServerName + "/w3svc/1/root";
try
{
// Talk to the IIS Metabase to read the MimeMap Metabase key
DirectoryEntry MimeMap = new DirectoryEntry(MetabasePath);
// Get the Mime Types as a collection
PropertyValueCollection pvc = MimeMap.Properties["MimeMap"];
// Add each Mime Type so we can display it sorted later
foreach (object Value in pvc)
{
// Convert to an IISOle.MimeMap - Requires a connection to IISOle
// IISOle can be added to the references section in VS.NET by selecting
// Add Reference, selecting the COM Tab, and then finding the
// Active DS Namespace provider
IISOle.MimeMap mimetypeObj = (IISOle.MimeMap)Value;
// Add the mime extension and type to our sorted list.
sl.Add(mimetypeObj.Extension, mimetypeObj.MimeType);
}
// Render the sorted MIME entries
if (sl.Count == 0)
Console.WriteLine("No MimeMap entries are defined at {0}!", MetabasePath);
else
foreach (string Key in sl.Keys)
Console.WriteLine("{0} : {1}", Key.PadRight(20), sl[Key]);
}
catch (Exception ex)
{
if ("HRESULT 0x80005006" == ex.Message)
Console.WriteLine(" Property MimeMap does not exist at {0}", MetabasePath);
else
Console.WriteLine("An exception has occurred: \n{0}", ex.Message);
}
// Convert to an IISOle.MimeMap - Requires a connection to IISOle
// IISOle can be added to the references section in VS.NET by selecting
// Add Reference, selecting the COM Tab, and then finding the
// Active DS Namespace provider
According to my googling: (lost the links, sorry)
The "Active DS IIS Namespace Provider" is part of the IIS installation.
After you install IIS you will see that in the list of options.
If you don't see it should be located at C:\windows\system32\inetsrv\adsiss.dll.
To install IIS:
click Start, Settings, Control Panel, Add or Remove Programs, Add or Remove Windows Components, select Internet Informatoin Services (IIS).
Most of the code I've seen uses some combination of these:
using System.IO;
using System.DirectoryServices; // Right-click on References, and add it from .NET
using System.Reflection;
using System.Runtime.InteropServices;
using System.Collections;
using IISOle;
using System.Collections.Specialized;
The Active DS Namespace might be under the COM tab when adding the reference.
I've written a small class based on the webmaster-toolkit.com list. This is to avoid using COM and the IIS route or any IIS references.
It uses an XML serialized list which contains about 400 mimetypes, so is usually more than enough unless you have really obscure mimetypes. In that case you can just add to the XML file.
The full solution can be found here. Here's a sample:
class Program
{
static void Main(string[] args)
{
var list = MimeType.Load();
MimeType mimetype = list.FirstOrDefault(m => m.Extension == "jpg");
}
}

Resources