Relayer implementation

This refactor introduces two major changes

1. Now creating the 'handler' delegate happens inside the endpoint
middleware. This allows you to short circuit even harder, AND to create
endpoint funcs that capture and use 'next' to rejoin the middleware
pipeline.

2. Relayered the implementation to have routing plug into the dispatcher.
It wasn't immediately apparent to me that this was the right thing to do,
but I think we will need to do things this way to deliver the kind of
back-compat experience we need to do.

The idea that I have is that 'attribute routing' will be the 'default'
entry in the dispatcher. Adding additional conventional routes or other
IRouter-based extensibility will be possible through adapters - but the
default experience will be to add items to the 'attribute route'.

So. We will need to port the attribute routing infrastructure to the
dispatcher library.

We may also need to make RVD into a subclass of something in the
dispatcher assembly.
This commit is contained in:
Ryan Nowak 2017-09-17 00:06:58 -07:00
parent b01072eb47
commit 134096d9cb
21 changed files with 462 additions and 216 deletions

View File

@ -1,17 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Dispatcher;
namespace DispatcherSample
{
public class DispatcherEndpoint : Endpoint
{
public DispatcherEndpoint(string displayName)
{
DisplayName = displayName;
}
public override string DisplayName { get; }
}
}

View File

@ -6,9 +6,6 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Dispatcher\Microsoft.AspNetCore.Dispatcher.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing.Abstractions\Microsoft.AspNetCore.Routing.Abstractions.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
</ItemGroup>

View File

@ -1,73 +1,70 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Dispatcher;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.AspNetCore.Routing.Dispatcher;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace DispatcherSample
{
public class Startup
{
private readonly static IInlineConstraintResolver ConstraintResolver = new DefaultInlineConstraintResolver(
new OptionsManager<RouteOptions>(
new OptionsFactory<RouteOptions>(
Enumerable.Empty<IConfigureOptions<RouteOptions>>(),
Enumerable.Empty<IPostConfigureOptions<RouteOptions>>())));
public void ConfigureServices(IServiceCollection services)
{
services.Configure<DispatcherOptions>(options =>
{
options.DispatcherEntryList = new List<DispatcherEntry>()
{
new DispatcherEntry
{
RouteTemplate = TemplateParser.Parse("{Endpoint=example}"),
Endpoints = new []
options.Dispatchers.Add(CreateDispatcher(
"{Endpoint=example}",
new RouteValuesEndpoint(
new RouteValueDictionary(new { Endpoint = "First" }),
async (context) =>
{
new RouteValuesEndpoint("example")
{
RequiredValues = new RouteValueDictionary(new { Endpoint = "First" }),
RequestDelegate = async (context) =>
{
await context.Response.WriteAsync("Hello from the example!");
}
},
new RouteValuesEndpoint("example2")
{
RequiredValues = new RouteValueDictionary(new { Endpoint = "Second" }),
RequestDelegate = async (context) =>
{
await context.Response.WriteAsync("Hello from the second example!");
}
},
}
},
await context.Response.WriteAsync("Hello from the example!");
},
Array.Empty<object>(),
"example"),
new RouteValuesEndpoint(
new RouteValueDictionary(new { Endpoint = "Second" }),
async (context) =>
{
await context.Response.WriteAsync("Hello from the second example!");
},
Array.Empty<object>(),
"example2")));
new DispatcherEntry
{
RouteTemplate = TemplateParser.Parse("{Endpoint=example}/{Parameter=foo}"),
Endpoints = new []
options.Dispatchers.Add(CreateDispatcher(
"{Endpoint=example}/{Parameter=foo}",
new RouteValuesEndpoint(
new RouteValueDictionary(new { Endpoint = "First", Parameter = "param1" }),
async (context) =>
{
new RouteValuesEndpoint("example")
{
RequiredValues = new RouteValueDictionary(new { Endpoint = "First", Parameter = "param1"}),
RequestDelegate = async (context) =>
{
await context.Response.WriteAsync("Hello from the example for foo!");
}
},
new RouteValuesEndpoint("example2")
{
RequiredValues = new RouteValueDictionary(new { Endpoint = "Second", Parameter = "param2"}),
RequestDelegate = async (context) =>
{
await context.Response.WriteAsync("Hello from the second example for foo!");
}
},
}
}
};
await context.Response.WriteAsync("Hello from the example for foo!");
},
Array.Empty<object>(),
"example"),
new RouteValuesEndpoint(
new RouteValueDictionary(new { Endpoint = "Second", Parameter = "param2" }),
async (context) =>
{
await context.Response.WriteAsync("Hello from the second example for foo!");
},
Array.Empty<object>(),
"example2")));
options.HandlerFactories.Add((endpoint) => (endpoint as RouteValuesEndpoint)?.HandlerFactory);
});
services.AddSingleton<UrlGenerator>();
@ -99,5 +96,11 @@ namespace DispatcherSample
await next.Invoke();
});
}
private static RequestDelegate CreateDispatcher(string routeTemplate, RouteValuesEndpoint endpoint, params RouteValuesEndpoint[] endpoints)
{
var dispatcher = new RouterDispatcher(new Route(new RouterEndpointSelector(new[] { endpoint }.Concat(endpoints)), routeTemplate, ConstraintResolver));
return dispatcher.InvokeAsync;
}
}
}

View File

@ -1,10 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Dispatcher
{
public abstract class Endpoint
{
public abstract string DisplayName { get; }
public abstract IReadOnlyList<object> Metadata { get; }
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Dispatcher
{
/// <summary>
/// A delegate which attempts to create a <see cref="Func{RequestDelegate, RequestDelegate}"/> for the selected <see cref="Endpoint"/>.
/// </summary>
/// <param name="endpoint">The <see cref="Endpoint"/> selected by the dispatcher.</param>
/// <returns>
/// A <see cref="Func{RequestDelegate, RequestDelegate}"/> that invokes the operation represented by the <see cref="Endpoint"/>, or <c>null</c>.
/// </returns>
public delegate Func<RequestDelegate, RequestDelegate> EndpointHandlerFactory(Endpoint endpoint);
}

View File

@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Dispatcher
{
public interface IDispatcherFeature
{
Endpoint Endpoint { get; }
Endpoint Endpoint { get; set; }
RequestDelegate RequestDelegate { get; }
RequestDelegate RequestDelegate { get; set; }
}
}

View File

@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
</ItemGroup>
</Project>

View File

@ -1,15 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Dispatcher
{
public class DispatcherEndpoint : Endpoint
public abstract class DispatcherBase
{
public DispatcherEndpoint(string displayName)
{
DisplayName = displayName;
}
public override string DisplayName { get; }
public abstract Task InvokeAsync(HttpContext httpContext);
}
}

View File

@ -1,15 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Routing.Template;
namespace Microsoft.AspNetCore.Dispatcher
{
public class DispatcherEntry
{
public IList<RouteValuesEndpoint> Endpoints { get; set; }
public RouteTemplate RouteTemplate { get; set; }
}
}

View File

@ -4,8 +4,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Dispatcher
@ -17,82 +15,35 @@ namespace Microsoft.AspNetCore.Dispatcher
public DispatcherMiddleware(IOptions<DispatcherOptions> options, RequestDelegate next)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
_options = options.Value;
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
foreach (var entry in _options.DispatcherEntryList)
var feature = new DispatcherFeature();
httpContext.Features.Set<IDispatcherFeature>(feature);
foreach (var entry in _options.Dispatchers)
{
var parsedTemplate = entry.RouteTemplate;
var defaults = GetDefaults(parsedTemplate);
var templateMatcher = new TemplateMatcher(parsedTemplate, defaults);
var values = new RouteValueDictionary();
foreach (var endpoint in entry.Endpoints)
await entry(httpContext);
if (feature.Endpoint != null || feature.RequestDelegate != null)
{
if (templateMatcher.TryMatch(httpContext.Request.Path, values))
{
if (!CompareRouteValues(values, endpoint.RequiredValues))
{
values.Clear();
}
else
{
var dispatcherFeature = new DispatcherFeature
{
Endpoint = endpoint,
RequestDelegate = endpoint.RequestDelegate
};
httpContext.Features.Set<IDispatcherFeature>(dispatcherFeature);
break;
}
}
break;
}
}
await _next(httpContext);
}
private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)
{
var result = new RouteValueDictionary();
foreach (var parameter in parsedTemplate.Parameters)
{
if (parameter.DefaultValue != null)
{
result.Add(parameter.Name, parameter.DefaultValue);
}
}
return result;
}
private bool CompareRouteValues(RouteValueDictionary values, RouteValueDictionary requiredValues)
{
foreach (var kvp in requiredValues)
{
if (string.IsNullOrEmpty(kvp.Value.ToString()))
{
if (values.TryGetValue(kvp.Key, out var routeValue) && !string.IsNullOrEmpty(routeValue.ToString()))
{
return false;
}
}
else
{
if (!values.TryGetValue(kvp.Key, out var routeValue) || !string.Equals(kvp.Value.ToString(), routeValue.ToString(), StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
}
return true;
}
}
}

View File

@ -1,12 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Dispatcher
{
public class DispatcherOptions
{
public IList<DispatcherEntry> DispatcherEntryList { get; set; }
public IList<RequestDelegate> Dispatchers { get; } = new List<RequestDelegate>();
public IList<EndpointHandlerFactory> HandlerFactories { get; } = new List<EndpointHandlerFactory>();
}
}

View File

@ -1,31 +1,56 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Dispatcher
{
public class EndpointMiddleware
{
private readonly DispatcherOptions _options;
private RequestDelegate _next;
public EndpointMiddleware(RequestDelegate next)
public EndpointMiddleware(IOptions<DispatcherOptions> options, RequestDelegate next)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
_options = options.Value;
_next = next;
}
public async Task Invoke(HttpContext context)
{
var feature = context.Features.Get<IDispatcherFeature>();
if (feature.RequestDelegate == null)
if (feature.Endpoint != null && feature.RequestDelegate == null)
{
await _next(context);
for (var i = 0; i < _options.HandlerFactories.Count; i++)
{
var handler = _options.HandlerFactories[i](feature.Endpoint);
if (handler != null)
{
feature.RequestDelegate = handler(_next);
break;
}
}
}
else
if (feature.RequestDelegate != null)
{
await feature.RequestDelegate(context);
}
await _next(context);
}
}
}

View File

@ -9,12 +9,13 @@
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
</ItemGroup>
</Project>

View File

@ -1,22 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Dispatcher
{
public class RouteValuesEndpoint : Endpoint
{
public RouteValuesEndpoint(string displayName)
{
DisplayName = displayName;
}
public override string DisplayName { get; }
public RequestDelegate RequestDelegate { get; set; }
public RouteValueDictionary RequiredValues { get; set; }
}
}

View File

@ -0,0 +1,73 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Dispatcher
{
public class SimpleEndpoint : Endpoint
{
public SimpleEndpoint(RequestDelegate requestDelegate)
: this(requestDelegate, Array.Empty<object>(), null)
{
}
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory)
: this(delegateFactory, Array.Empty<object>(), null)
{
}
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata)
: this(requestDelegate, metadata, null)
{
}
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata)
: this(delegateFactory, metadata, null)
{
}
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata, string displayName)
{
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
if (requestDelegate == null)
{
throw new ArgumentNullException(nameof(requestDelegate));
}
DisplayName = displayName;
Metadata = metadata.ToArray();
DelegateFactory = (next) => requestDelegate;
}
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata, string displayName)
{
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
if (delegateFactory == null)
{
throw new ArgumentNullException(nameof(delegateFactory));
}
DisplayName = displayName;
Metadata = metadata.ToArray();
DelegateFactory = delegateFactory;
}
public override string DisplayName { get; }
public override IReadOnlyList<object> Metadata { get; }
public Func<RequestDelegate, RequestDelegate> DelegateFactory { get; }
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Dispatcher;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Dispatcher
{
public class RouteValuesEndpoint : Endpoint
{
public RouteValuesEndpoint(RouteValueDictionary requiredValues, RequestDelegate requestDelegate)
: this(requiredValues, requestDelegate, Array.Empty<object>(), null)
{
}
public RouteValuesEndpoint(RouteValueDictionary requiredValues, RequestDelegate requestDelegate, IEnumerable<object> metadata)
: this(requiredValues, requestDelegate, metadata, null)
{
}
public RouteValuesEndpoint(
RouteValueDictionary requiredValues,
RequestDelegate requestDelegate,
IEnumerable<object> metadata,
string displayName)
{
if (requiredValues == null)
{
throw new ArgumentNullException(nameof(requiredValues));
}
if (requestDelegate == null)
{
throw new ArgumentNullException(nameof(requestDelegate));
}
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
RequiredValues = requiredValues;
HandlerFactory = (next) => requestDelegate;
Metadata = metadata.ToArray();
DisplayName = displayName;
}
public override string DisplayName { get; }
public override IReadOnlyList<object> Metadata { get; }
public Func<RequestDelegate, RequestDelegate> HandlerFactory { get; set; }
public RouteValueDictionary RequiredValues { get; set; }
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Dispatcher;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Dispatcher
{
/// <summary>
/// An adapter to plug an <see cref="IRouter"/> into a dispatcher.
/// </summary>
public class RouterDispatcher : DispatcherBase
{
private readonly IRouter _router;
public RouterDispatcher(IRouter router)
{
if (router == null)
{
throw new ArgumentNullException(nameof(router));
}
_router = router;
}
public async override Task InvokeAsync(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var routeContext = new RouteContext(httpContext);
await _router.RouteAsync(routeContext);
}
}
}

View File

@ -0,0 +1,110 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Dispatcher;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Dispatcher
{
public class RouterEndpointSelector : IRouter, IRouteHandler
{
private readonly RouteValuesEndpoint[] _endpoints;
public RouterEndpointSelector(IEnumerable<RouteValuesEndpoint> endpoints)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
}
_endpoints = endpoints.ToArray();
}
public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (routeData == null)
{
throw new ArgumentNullException(nameof(routeData));
}
var dispatcherFeature = httpContext.Features.Get<IDispatcherFeature>();
if (dispatcherFeature == null)
{
throw new InvalidOperationException(Resources.FormatDispatcherFeatureIsRequired(
nameof(HttpContext),
nameof(IDispatcherFeature),
nameof(RouterEndpointSelector)));
}
for (var i = 0; i < _endpoints.Length; i++)
{
var endpoint = _endpoints[i];
if (CompareRouteValues(routeData.Values, endpoint.RequiredValues))
{
dispatcherFeature.Endpoint = endpoint;
return null;
}
}
return null;
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return null;
}
public Task RouteAsync(RouteContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var handler = GetRequestHandler(context.HttpContext, context.RouteData);
if (handler != null)
{
context.Handler = handler;
}
return Task.CompletedTask;
}
private bool CompareRouteValues(RouteValueDictionary values, RouteValueDictionary requiredValues)
{
foreach (var kvp in requiredValues)
{
if (string.IsNullOrEmpty(kvp.Value.ToString()))
{
if (values.TryGetValue(kvp.Key, out var routeValue) && !string.IsNullOrEmpty(routeValue.ToString()))
{
return false;
}
}
else
{
if (!values.TryGetValue(kvp.Key, out var routeValue) || !string.Equals(kvp.Value.ToString(), routeValue.ToString(), StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
}
return true;
}
}
}

View File

@ -17,6 +17,8 @@ Microsoft.AspNetCore.Routing.RouteCollection</Description>
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Routing.Abstractions\Microsoft.AspNetCore.Routing.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher\Microsoft.AspNetCore.Dispatcher.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -402,6 +402,20 @@ namespace Microsoft.AspNetCore.Routing
internal static string FormatTemplateRoute_Exception(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_Exception"), p0, p1);
/// <summary>
/// The '{0}' has no '{1}'. '{2}' requires a dispatcher.
/// </summary>
internal static string DispatcherFeatureIsRequired
{
get => GetString("DispatcherFeatureIsRequired");
}
/// <summary>
/// The '{0}' has no '{1}'. '{2}' requires a dispatcher.
/// </summary>
internal static string FormatDispatcherFeatureIsRequired(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherFeatureIsRequired"), p0, p1, p2);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -201,4 +201,7 @@
<data name="TemplateRoute_Exception" xml:space="preserve">
<value>An error occurred while creating the route with name '{0}' and template '{1}'.</value>
</data>
<data name="DispatcherFeatureIsRequired" xml:space="preserve">
<value>The '{0}' has no '{1}'. '{2}' requires a dispatcher.</value>
</data>
</root>