Technolog

Blogging over technologie.
Welcome to Technolog Sign in | Join | Help

Front Page News

  • How to convert WCF REST services to Web API 2 (ASP.NET)

     

    How can I convert my WCF Rest services to Web API 2 without telling customers to implement changes as well?

    Maintaining existing software is more hard to reach than to create new one from scratch. Right?

    Here is my experience, which might gain some time for you.

    First, my customer already used VB.NET in this project, so don’t blame me for using VB.NET instead of C# :)

    The existing Services (called ‘Controllers’ in Web API terms) looked like this…

    Service Body definition+attributes

    <AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Required)>
    <ServiceContract(Namespace:="
    https://mydomain.blah.nl/Service")>
    <ServiceBehavior(
         Name:="MobileService",
         ConcurrencyMode:=ConcurrencyMode.Multiple,
         MaxItemsInObjectGraph:=Integer.MaxValue)>
    <DataContractFormat()>
    Partial Public Class MobileService
         Inherits ServiceBase
    ….

    ServiceBase is just a base class which has some methods for shortening getting the MemberShip User and Booleans like ‘IsAdmin’. For brevity, you don’t need to see it.

    Service actions

    They look like this:

    <OperationContract()>
    <WebInvoke(Method:="GET", UriTemplate:="Product/{productid}")>
    Public Function GetProduct(productid As String) As Product
        If MembershipUser Is Nothing Then Throw New UnauthorizedAccessException()
        Return New Product(CInt(productid))
    End Function

    There are several attributes, such as WebGet etc, which you quickly recognize having counterparts in WebAPI2

    WCF REST ?

    You know, REST = HTTP [ACTION VERB(S)] URL + [body].
    WCF had an Attribute AspNetCompatibilityRequirements which enabled you to even have a Session State and to run within the ASP.NET pipeline. However, REST should not have a ‘session state’.

    The response can depending on the Http Header Accept be either application/json, or application/xml

    JSON is the easiest stuff, because it does not deal with XML namespaces. However, if an endpoint client requests application/xml, the service might return a constructed rootname element, using the controller name as a basename. Such as  <ArrayOfHardwareService.Category xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns=http://schemas.datacontract.org/2004/07/Yadda">

    As you see, because my WCF REST controller was named HardwareService, it is being used in the XML output. If you have existing customers, you cannot just modify it to be, say, ‘ArrayOfHardwareController’.

    Now, a real service implementor, would advice you to use CollectionDataContract attributes. Please do so, for new from scratch projects, However, again, I don’t want to redefine my existing object model, which can be a lot of work!

    TIP 1: Use this excelent hack, which really works like a charm.

    http://www.strathweb.com/2013/02/but-i-dont-want-to-call-web-api-controllers-controller/

    In VB.NET (I guess, your gasping to see it?) this little helper is like this:

    Public Class CustomHttpControllerTypeResolver
          Inherits DefaultHttpControllerTypeResolver
          Public Sub New()
              MyBase.New(Function(T As Type)
                             If T Is Nothing Then Throw New ArgumentNullException("t")
                             Return T.IsClass AndAlso T.IsVisible AndAlso Not T.IsAbstract AndAlso GetType(ApiController).IsAssignableFrom(T) AndAlso GetType(IHttpController).IsAssignableFrom(T)
                         End Function)
          End Sub
      End Class

    In Application_OnStart (or so) you add this

    Web.Http.GlobalConfiguration.Configuration.Services.Replace(GetType(System.Web.Http.Dispatcher.IHttpControllerTypeResolver), New Api.CustomHttpControllerTypeResolver())

    In WebApiConfig use this:

    'override the suffix 'Controller' requirement
    Dim suffix = GetType(DefaultHttpControllerSelector).GetField("ControllerSuffix", BindingFlags.Static Or BindingFlags.Public)
    If suffix IsNot Nothing Then suffix.SetValue(Nothing, String.Empty)
    I really like this hack above! Because we don’t need to mess with caching the WebAPI2 controllers ourselves. Which is indeed madness to implement ourselves (mostly).

    Now the next challenge. Most companies have JSON/XML services available as service for both end-to-end points, but also as data-source in websites, which consume it using javascript.
    In ASP.NET, you probably have some FormsAuthentication mechanism, which is cookie based and optimized for persisting an authenticated session.
    WebAPI 2 Controllers do support this, using the Authorize attribute, however, you’ll discover, it does NOT support Basic authentication, which is in combination with SSL, a good candidate for encryption over data for most B-2-B endpoints..

    So you need a ‘hack’ to elegantly support BOTH FormsAuthentication, and Basic Authentication. Note, the sample from the Web I used it from, ONLY supports BasicAuthentication, incorrectly calling it ‘Mixed’ support, which it was not. My code however, does support both FormsAuthentication as well as Basic authentication.

    Note 1: It does not support the FormsAuthentication challenge sequence, which I don’t need since one normally does not log on using a browser to a JSON Service URL/endpoint. So, MyBase.IsAuthorized(actionContext) does the trick. Thus you don’t have to validate the .aspxauth cookie (Part of FormsAuthentication) yourselves.

    Note 2: You must finish the TODO comment, otherwise, the attribute won’t work for you.

    TIP 2 Use the attribute below, as a replacement for the Authenticate attribute.

    ''' <summary> 
    ''' HTTP authentication filter for ASP.NET Web API
    ''' </summary>
    ''' <seealso cref="
    http://piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/"/>
    Public MustInherit Class BasicHttpAuthorizeAttribute
        Inherits AuthorizeAttribute

        Private Const BasicAuthResponseHeader = "WWW-Authenticate"
        Private Const BasicAuthResponseHeaderValue = "Basic"

        Public Overrides Sub OnAuthorization(actionContext As HttpActionContext)

            If (actionContext Is Nothing) Then
                Throw New ArgumentNullException("actionContext")
            End If
            If (AuthorizationDisabled(actionContext) OrElse MyBase.IsAuthorized(actionContext) OrElse AuthorizeRequest(actionContext.ControllerContext.Request)) Then
                Return
            End If

            HandleUnauthorizedRequest(actionContext)
        End Sub

        Protected Overrides Sub HandleUnauthorizedRequest(actionContext As HttpActionContext)

            If (actionContext Is Nothing) Then
                Throw New ArgumentNullException("actionContext")
            End If
            actionContext.Response = CreateUnauthorizedResponse(actionContext.ControllerContext.Request)
        End Sub

        Private Shared Function CreateUnauthorizedResponse(request As HttpRequestMessage) As HttpResponseMessage

            Dim result = New HttpResponseMessage() With
                         {
                            .StatusCode = HttpStatusCode.Unauthorized,
                            .RequestMessage = request
                        }

            'we need to include WWW-Authenticate header in our response,
            'so our client knows we are using HTTP authentication
            result.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue)
            Return result
        End Function

        Private Shared Function AuthorizationDisabled(actionContext As HttpActionContext) As Boolean
            'support New AllowAnonymousAttribute
            If Not actionContext.ActionDescriptor.GetCustomAttributes(Of AllowAnonymousAttribute).Any() Then
                Return actionContext.ControllerContext.ControllerDescriptor().GetCustomAttributes(Of AllowAnonymousAttribute).Any()
            Else
                Return True
            End If
        End Function

        Private Function AuthorizeRequest(request As HttpRequestMessage) As Boolean

            Dim authValue = request.Headers.Authorization
            If (authValue Is Nothing OrElse String.IsNullOrWhiteSpace(authValue.Parameter) OrElse
                String.IsNullOrWhiteSpace(authValue.Scheme) OrElse
                authValue.Scheme <> BasicAuthResponseHeaderValue) Then

                Return False
            End If

            Dim parsedHeader = ParseAuthorizationHeader(authValue.Parameter)
            If parsedHeader Is Nothing Then
                Return False
            End If
            Dim principal As IPrincipal = Nothing
            If TryCreatePrincipal(parsedHeader(0), parsedHeader(1), principal) Then

                HttpContext.Current.User = principal
                Return CheckRoles(principal) AndAlso CheckUsers(principal)

            Else
                Return False
            End If
        End Function

        Private Function CheckUsers(principal As IPrincipal) As Boolean

            Dim usrs = UsersSplit
            If usrs.Length = 0 Then Return True
            'NOTE: This is a case sensitive comparison
            Return usrs.Any(Function(u) principal.Identity.Name = u)
        End Function

        Private Function CheckRoles(principal As IPrincipal) As Boolean

            Dim rls = RolesSplit
            If rls.Length = 0 Then Return True
            Return rls.Any(Function(r) principal.IsInRole(r))
        End Function

        Private Shared Function ParseAuthorizationHeader(authHeader As String) As String()

            Dim credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split(":"c)
            If (credentials.Length <> 2 OrElse String.IsNullOrEmpty(credentials(0)) OrElse
                String.IsNullOrEmpty(credentials(1))) Then
                Return Nothing
            End If
            Return credentials
        End Function

        Protected ReadOnly Property RolesSplit() As String()
            Get
                Return SplitStrings(Roles)
            End Get
        End Property

        Protected ReadOnly Property UsersSplit() As String()
            Get
                Return SplitStrings(Users)
            End Get
        End Property

        Protected Shared Function SplitStrings(input As String) As String()
            If String.IsNullOrWhiteSpace(input) Then Return New String() {}
            Dim result = input.Split(","c).Where(Function(s) Not String.IsNullOrWhiteSpace(s.Trim()))
            Return result.Select(Function(s) s.Trim()).ToArray()
        End Function

        ''' <summary>
        ''' Implement to include authentication logic and create IPrincipal
        ''' </summary>
        Protected MustOverride Function TryCreatePrincipal(user As String, password As String, ByRef principal As IPrincipal) As Boolean
    End Class
    Public Class MembershipHttpAuthorizeAttribute
        Inherits BasicHttpAuthorizeAttribute

        ''' <summary>
        ''' Implement to include authentication logic and create IPrincipal
        ''' </summary>
        Protected Overrides Function TryCreatePrincipal(user As String, password As String, ByRef principal As IPrincipal) As Boolean

            principal = Nothing
            If Not Membership.ValidateUser(user, password) Then
                Return False
            End If
            Dim rles = Web.Security.Roles.Provider.GetRolesForUser(user)

    'TODO: You must assign here your OWN principal       

            'principal = New GenericPrincipal(New GenericIdentity(user), roles)
            Return True
        End Function

    End Class

    RESULT

    Final Controller body

    <RoutePrefix("api/blah"), MembershipHttpAuthorize(Roles:=aspnet_Role.blahRole+","+ aspnet_Role.BlahRole2)>
    Partial Public Class MobileService
        Inherits Api.ApiBaseController

    As you can see, I don’t have the ‘ Controller’  suffix for my Web API2 controller, and I even can use the RoutePrefix attribute. Second, I did not use ‘ Authorize’  attribute, but the mixed MembershipHttpAuthorize attribute.

    Controller Actions

    ' <summary>
    ' Looks up some data by ID.
    ' </summary>
    <HttpGet, Route("Product/{productid}")>
    Public Function GetProduct(productid As Integer) As IHttpActionResult

        Return Ok(New Product(productid))
    End Function

    I don’t know if WCF could support non-string parameters, I don’t want to know, anyway, as above, you see, it’s quite simple.

    In this case, I like to have a function of type IHttpActionResult, because than I easily can return BadRequest or NotFound(). For more information about the possibilities, have a look at http://www.asp.net/web-api/overview

     

    Quirks.

    Sometimes, it seemd that JQuery simply did not behave nicely with a REST / JSON call (this also was the case in the WCF implementation of my client), that only returns HTTP 200 (OK) with no return body. So, I found out, that service reliability improved by returning A value such as Ok(True). So, basically, always define your actions with a specific type, not being ‘void’ or ‘sub’. OK?

    Another issue occurring with HttpPost and HttpPut is when parameters are partly from a Uri and partly from body. WCF could figure this out, but strangely enough, you must help Web Api 2 using attributes FromUriAttribute and FromBodyAttribute. I did not have time to figure out when this was needed, or not but added the attribute.

    So:

    <HttpPost, Route("Network/{networkid}/GetCustomerConsumer/")>
    Public Function GetCustomerConsumer(networkid As Integer, <FromBody> req As GetCustomerConsumerRequest) As IHttpActionResult
        Try
            Return Ok(GetCustomer(networkid, req))
        Catch ex As Exception
            Return BadRequest(ex.Message)
        End Try
    End Function

    In the sample below, it certainly was necessary to define a ‘ dummy’  class, to pass simple types like status, which is an integer.

    <HttpPost, Route("status/{myid}")>
    Public Function SetStatus(myid As Integer, <FromBody> dm As Dummy) As IHttpActionResult

    Public Class Dummy
         Public remark As String
         Public status As Integer
    End Class

    WebApi Config

    You need to adapt JSON serialization as well. Try to keep it using the NewtonSoft.Json serializer, instead of the  json.UseDataContractJsonSerializer=true!

    You need to set MicrosoftDateFormat to be compatible with the WCF REST JSON output (instead of ISO). Second, you need to output Null Values as well. There also is an issue, with TimeZone support in WCF, which is unspecified I believe, which leads to crazy bugs with DateTime output, anyway, without solving that WCF issue in this article, you need ‘ Unspecified’  here as well.

    Another nice feature while debugging/developing, is Indented JSON which allows you to easily read your JSON output using your favorite Browser Netwerk trace.

       Public Class WebApiConfig
            Public Shared Sub Register(ByVal config As HttpConfiguration)
                config.MapHttpAttributeRoutes()

                Dim json = config.Formatters.JsonFormatter
                json.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat          
                json.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Include
                json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Unspecified
    #If DEBUG Then
                json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented
    #End If
                'override the suffix 'Controller' requirement
                Dim suffix = GetType(DefaultHttpControllerSelector).GetField("ControllerSuffix", BindingFlags.Static Or BindingFlags.Public)
                If suffix IsNot Nothing Then suffix.SetValue(Nothing, String.Empty)  

            End Sub
        End Class

    23 hours, 31 minutes ago by eprogrammer to Egbert Nierop technolog
  • The Ultimate T-SQL String Splitter (function)

     

    I just want to share this function, since there are a lot of version around, which are not resistant against zero positions advance, for instance, if you split ‘1,2,3’ into a table, it would be find, but what if one element is empty, such as ‘1,,3’? This function deals with it setting returning a null element.

    Usage:

    SELECT * FROM [udf_SplitVarchar2Table]('one,two,three', ',')

    returns:

    ALTER FUNCTION [dbo].[udf_SplitVarchar2Table]
    (
        @List varchar(max),
        @delimiter VARCHAR(10)
    )

    RETURNS
        @Values TABLE(col VARCHAR(512))
    AS

    BEGIN 
        IF @List IS NULL OR LEN(@List) = 0 RETURN;
     
      SET @List = replace(@List,CHAR(39)+CHAR(39),CHAR(39))
     
      DECLARE @Index INT=1; 
      DECLARE @ItemValue varchar(100);  
      DECLARE @pos INT = 1;
      DECLARE @l INT = LEN(@List);

      WHILE @Index > 0   
        BEGIN        
          SET @Index = CHARINDEX(@Delimiter,@List, @pos);  
       
          IF @Index  > 0 
                IF (@index- @pos> 0)
                    SET @ItemValue = SUBSTRING(@List,@pos, @index- @pos );
                ELSE
                    SET @ItemValue=NULL;
          ELSE
            IF (@l-@pos+1)>0
                SET @ItemValue =SUBSTRING( @List, @pos, @l-@pos+1) ;
            ELSE
                SET @ItemValue = NULL;

          INSERT INTO @Values (col) VALUES (@ItemValue);    
          SET @pos = @index+1;
        END
        RETURN;
    END

    08-28-2014, 12:31 by eprogrammer to Egbert Nierop technolog
  • How to read a HTML page from a remote site using VBA/.NET into a Htmlocument

     

    There are a lot of ways to read and parse HTML, the better tricks, don’t use IE itself, since this will deliver automation errors and waste memory.

    I’m for 99% of my time into .NET programming, but still, one of my hobbies use an Access 2013 database and thus, a VBA codebase, yummy! And to get powerfeatures, I compiled a tlb to have interfaces like IPersistStreamInit, IStream etc. (it’s called odl compiling and requires  MkTypLib.EXE, not midl.exe!)

    Now here is a neat way to fetch/get a plain HTML text and load it into a HTMLDocument without any dependency on IE automation. You’re a smart non-lazy programmer (right?) so you get the idea for C# as well since you need IPersistStreamInit there as well. It’s COM interop, dude!

    Public Function HttpGet(ByRef url As String) As mshtml.HTMLDocument
        Dim xmlHttp As MSXML2.ServerXMLHTTP60
        Set xmlHttp = CreateObject("MSXML2.ServerXMLHTTP")
        xmlHttp.Open "GET", url, False
        xmlHttp.send
       'set return value
        Set HttpGet = New HTMLDocument
        Dim stream As adodb.stream
        Set stream = CreateObject("ADODB.Stream")
        Dim istrea As IPersistStreamInit
       
       'get interface IPersistStreamInit from HTMLDocument
        Set istrea = HttpGet
       
       'write the muke using a binary array (bytes)
        stream.Type = adTypeBinary
        stream.Open
        stream.write xmlHttp.responseBody
       'reset stream
        stream.position = 0
        'load the muke into the HTMLDocument
        istrea.Load stream

        Dim s As Single
        s = Timer

       'fake body onload ready
        Do Until Timer - s > 10 Or HttpGet.ReadyState = "complete"
            DoEvents        
        Loop

    End Function

    07-11-2014, 10:43 by eprogrammer to Egbert Nierop technolog
  • T-SQL Alternative to hexadecimal binary strings?

    I found an easy way to have binary parameters as base64 encoded string. You might wonder, why bother?

    Well, in a a well used environment, size and compactness of data over the wire, still matters! Because a binary value is sent as a hexadecimal over the wire; Hexadecimals are 4 times the size of one byte. Base64 encoded strings however, just need +/- 3 times the size of one byte. .

    example:

    EXEC proc_receiveMyBlob 0xA05FDAF  (etc) 

     The stored procedure itself would have this signature:

    CREATE PROC  @myBLob varbinary(max)    -- or image whatever

    BEGIN 

       INSERT INTO tblMyBlobs VALUES(@myBlob);

    END 

    The trick:

     CREATE PROC  @myBLob xml  -- <-- use the xml T-SQL data type

    BEGIN 

    -- remember, the binary field in SQL must not be changed to xml, keep it as binary! 

       INSERT INTO tblMyBlobs VALUES(@myBlob.value('xs:base64Binary(.)', 'varbinary(max)') );

    END  

     

    The call to the stored proc (obviously) looks something like this:

    EXEC proc_receiveMyBlob 'SGVsbG8gQmFzZTY0' 

    or if you like:

    EXEC proc_receiveMyBlob '<data>SGVsbG8gQmFzZTY0</data>' 

     

    02-12-2014, 8:28 by eprogrammer to Egbert Nierop technolog
    Filed under: , ,
  • SiteMaps made easy

     

    If you’re a site admin or asp.net developer for an internet site, you certainly need to look into sitemaps, if you want to perform SEO.

    It’s not necessary to  simply crawl your own site and then to give every page a priority, but consider this for a forum or other pages which are irregularly or often updated. If you don’t want to have crawlers do unneeded roundtrips, implement a sitemap.

    ‘robots.txt’ should contain a reference to your map eg. Sitemap: http://www.myfantasticsite.com/sitemap.xml

    Ideas of this class, written using C#, can be found anywhere on the net. However, as some might know me, I like it to be finished and neat and a self-supporting class ready for usage (e.g. it must not be written to a string to add or remove wished attributes that the serializer could not handle).

    The following things are solved. 
    Since  ‘changefreq’ and ‘lastmod’ and ‘priority’ are optional values, you don’t want the XmlSerializer to create empty tags!
    This is done by adding a DefaultValue attribute. It will cause XmlSerializer to check the current value against the default value. If they are equal, it is considered to be an empty non existing tag. Remember, that the defaultvalues need to be out of the range of possible values! Therefore, EnumChangeFreq contains an extra member ‘notused’
    Remember, the Xml.Serialization name space, offers the tools to get it done without converting your loading your XML in to some XmlDocument class.

    You can use the class as follows.
    UrlSet retVal = new UrlSet();

    retVal.AddUrl(new Url() { LastModifiedDateTime = DateTime.Now.ToUniversalTime(), Loc = “http://www.myfantasticsite.com/blah.aspx”) });

    Retrieve the XML sitemap string.
    string xml = null;
    using (var io = (MemoryStream)retVal.ToStream())
    {
                    xml =  new UTF8Encoding().GetString(io.ToArray());
    }

    or, to write it directly to an output stream

    using (var io = (MemoryStream)retVal.ToStream())
    {

    //todo: Deal with Response.Cache, etag and last modified to avoid unnecessary round trips.
    Response.ContentType = “text/xml”;
    Response.CharSet = “utf-8”;
    Response.BinaryWrite(retVal.ToArray());

    }

    using System;
    using System.Xml;
    using System.Xml.Serialization;
    using System.ComponentModel;
    using System.IO;

    namespace adccure
    {

        public enum EnumChangeFreq
        {
            notset,
            always,
            hourly,
            daily,
            weekly,
            monthly,
            yearly,
            never
        }

    [

    [XmlRoot(ElementName = "urlset", Namespace = SCHEMA_SITEMAP)]
    public sealed class UrlSet
    {
        [XmlNamespaceDeclarations]
        public XmlSerializerNamespaces xmlns;
        private const string XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance";
        private const string SCHEMA_SITEMAP = "http://www.sitemaps.org/schemas/sitemap/0.9";

        private Url[] _url;

        public UrlSet()
        {
            _url = new Url[0];
            xmlns = new XmlSerializerNamespaces();
            xmlns.Add("xsi", XSI_NAMESPACE);
            SchemaLocation = SCHEMA_SITEMAP + " " + "http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd";

        }
        [XmlAttribute(AttributeName = "schemaLocation", Namespace = XSI_NAMESPACE)]
        public string SchemaLocation;

        public void AddUrl(Url url)
        {
            int l = _url.Length + 1;
            Array.Resize(ref _url, l);
            _url[l - 1] = url;
        }

        [XmlElement(ElementName = "url")]
        public Url[] url
        {
            get { return _url; }
            set { _url = value; } //bogus
        }
        /// <summary>
        /// serializes the UrlSet to a sitemap.xsd conform string ready for saving to disk.
        /// </summary>
        /// <returns>a Stream object</returns>
        public Stream ToStream()
        {
            XmlSerializer xmlser = new XmlSerializer(GetType());
            var io = new MemoryStream();
            xmlser.Serialize(new StreamWriter(io, Encoding.UTF8), this);
            io.Position = 0;
            return io;
        }
    }

        public sealed class Url
        {
            private string _loc;
            private DateTime _lastmod;
            private float _priority;
            private EnumChangeFreq _changefreq;

            public Url()
            {
                //setting defaults
                _changefreq = EnumChangeFreq.notset;
                _priority = 0.0F;
                _lastmod = DateTime.MinValue;
            }

            [XmlElement(ElementName = "loc")]
            public string Loc
            {
                get
                {
                    return _loc;
                }

                set
                {
                    if (string.IsNullOrEmpty(value))
                    {
                        throw new ArgumentNullException();
                    }
                    if (value.Length < 12 || value.Length > 2048)
                    {
                        throw new ArgumentException("loc must be between 12 and 2048 in length");
                    }
                    _loc = value;
                }
            }
            [XmlElement(ElementName = "lastmod"), DefaultValue(typeof(DateTime), "1-1-0001")]
            public DateTime LastModifiedDateTime
            {
                get
                {
                    return _lastmod;
                }

                set
                {
                    _lastmod = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Kind);

                }
            }
            [XmlElement(ElementName = "changefreq")]
            [DefaultValue(EnumChangeFreq.notset)]
            public EnumChangeFreq ChangeFreq
            {
                get
                {
                    return _changefreq;
                }

                set
                {
                    _changefreq = value;
                }
            }
            [XmlElement(ElementName = "priority")]
            [DefaultValue(0.0F)]
            public float Priority
            {
                get
                {
                    return _priority;
                }

                set
                {
                    if (value < 0 || value > 1.0)
                    {
                        throw new ArgumentException("Must be between 0 and 1");
                    }
                    _priority = value;
                }
            }
        }
    }

    Tags van Technorati: ,,
    01-31-2010, 18:12 by eprogrammer to Egbert Nierop technolog

Who is Online

There are 14 guest(s) online. Currently there are no online users.

Technolog is vernieuwd

Wil je ook een weblog? Meld je dan gratis aan.

Technorati Zoeken

Recent Additions

  • Sunset in Seattle
Powered by Community Server, by Telligent Systems